1use dioxus::core::current_scope_id;
4use dioxus::prelude::*;
5use std::{cell::Cell, fmt, rc::Rc};
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub struct Id(String);
10impl Id {
11 #[must_use]
13 pub fn as_str(&self) -> &str {
14 &self.0
15 }
16 #[must_use]
19 pub(crate) fn suffixed(&self, suffix: &'static str) -> Self {
20 Self(format!("{}-{suffix}", self.0))
21 }
22}
23impl TryFrom<&str> for Id {
24 type Error = InvalidId;
25 fn try_from(value: &str) -> Result<Self, Self::Error> {
28 if value.is_empty() || value.chars().any(|c| c.is_whitespace() || c.is_control()) {
29 Err(InvalidId)
30 } else {
31 Ok(Self(value.to_owned()))
32 }
33 }
34}
35impl TryFrom<String> for Id {
36 type Error = InvalidId;
37 fn try_from(value: String) -> Result<Self, Self::Error> {
38 Self::try_from(value.as_str())
39 }
40}
41impl fmt::Display for Id {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 f.write_str(&self.0)
44 }
45}
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct InvalidId;
49impl fmt::Display for InvalidId {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.write_str("id must be non-empty without whitespace or controls")
52 }
53}
54impl std::error::Error for InvalidId {}
55
56#[derive(Clone)]
57pub(crate) struct IdAllocator {
58 prefix: usize,
59 next: Rc<Cell<u64>>,
60}
61impl IdAllocator {
62 pub(crate) fn new() -> Self {
63 Self {
64 prefix: current_scope_id().0,
65 next: Rc::new(Cell::new(0)),
66 }
67 }
68 fn next(&self) -> Id {
69 let next = self.next.get();
70 self.next.set(next.saturating_add(1));
71 Id(format!("fc-{}-{next}", self.prefix))
72 }
73}
74
75#[must_use]
80pub fn use_id() -> Id {
81 let allocator = use_hook(|| {
82 try_consume_context::<IdAllocator>().unwrap_or_else(|| provide_context(IdAllocator::new()))
83 });
84 use_hook(|| allocator.next())
85}