1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! How a component mints the [`ElementId`]s it hands to gpui.
//!
//! # The rule
//!
//! **An element's id must be unique among everything drawn in a frame, not
//! just among that element's own parts.**
//!
//! 1. A component that stands on its own takes its id from its caller. Every
//! interactive component in this port already does: `Button::new(id)`,
//! `Checkbox::new(id)`, `Modal::new(id)`.
//! 2. Every *part* of that component hangs its id off the component's id with
//! [`scoped`], and every repeated part adds its position with [`indexed`].
//! `"dismiss"` is not an id; `scoped(&self.id, "dismiss")` is.
//!
//! A constant — `div().id("close")` — satisfies neither. It is the same id for
//! every instance of the component, so two of them on one page are one element
//! as far as gpui is concerned, and it is only ever correct by accident of an
//! ancestor the component does not control.
//!
//! # Why it matters here
//!
//! gpui keys two separate things on an element's *whole* id path (its
//! `GlobalElementId`):
//!
//! - **Element state.** `Window::with_element_state`, and therefore
//! `Window::use_keyed_state` and every `util` helper built on it, stores
//! per-element state under that path. Two elements with the same path share
//! one slot, silently: open flags, hover state, scroll offsets and — through
//! `util::tab_stop_handle` — *the focus handle itself* bleed between them.
//! - **Accessibility node ids.** gpui hashes the path into an
//! `accesskit::NodeId`, so a duplicate path collides two a11y nodes. That
//! half only bites once an element also reports a role, which is why a
//! duplicate id can sit in this crate looking harmless today. Scope the ids
//! first, add the roles second.
//!
//! Most components here are `RenderOnce` builders. A `RenderOnce` is inlined
//! into its parent's element tree and pushes *nothing* onto the id path, and
//! `use_keyed_state` calls at the top of `render` run before the component's
//! own root `div().id(..)` exists. So a component's keys are effectively
//! window-global, and "is this id unique?" cannot be answered by reading the
//! component. Deriving the id is what makes the answer local.
//!
//! # Why the structured variants beat `format!`
//!
//! The obvious spelling is `ElementId::Name(format!("{parent}-{part}").into())`,
//! and it is ambiguous. The separator is not reserved, so parent `"a-b"` with
//! part `"c"` and parent `"a"` with part `"b-c"` flatten to the identical key
//! `"a-b-c"`: two different elements, one state slot, one focus handle, no
//! error. Component ids in this port come from callers and gallery pages, which
//! spell them with hyphens all the time, so that is not a theoretical clash.
//!
//! [`ElementId::NamedChild`] keeps the parent id as *structure* — a separate
//! field, compared and hashed on its own — so those two cases cannot fold
//! together. It also costs no allocation: it clones an `Arc<ElementId>` and a
//! `SharedString` instead of formatting a fresh `String` for every part of
//! every component on every frame.
//!
//! The one thing it deliberately keeps is the `format!` spelling's `Display`:
//! `NamedChild` renders as `{parent}-{part}`, so an id that reaches a log line
//! or a `debug_selector` reads exactly as it did before.
use Arc;
use ;
/// The id of a named part of the element identified by `parent`.
///
/// This is the workhorse. `parent` is the component's caller-supplied id (or
/// another part's id, for a part of a part), and `part` names the piece within
/// it — `"trigger"`, `"panel"`, `"focus"`, `"checked"`.
///
/// ```
/// use gpui::ElementId;
/// use herogpui_core::element_id::scoped;
///
/// let dialog: ElementId = "confirm".into();
/// let close = scoped(&dialog, "close");
///
/// assert_eq!(close.to_string(), "confirm-close");
/// // Nesting is structure, not text: these are three distinct ids.
/// assert_ne!(close, ElementId::Name("confirm-close".into()));
/// assert_ne!(scoped(&scoped(&dialog, "a"), "b"), scoped(&dialog, "a-b"));
/// ```
/// The id of the `index`th of a repeated part of `parent`.
///
/// For rows, cells, tabs, options, segments — anything a component renders once
/// per item. The position is the only thing separating siblings, and `parent`
/// is what separates two of the collection.
///
/// The index rides as its own segment rather than as
/// [`ElementId::NamedInteger`], because `gpui-pre` 0.3.3 has no variant carrying
/// both a parent `ElementId` and an integer: `NamedInteger`'s name is a
/// `SharedString`, so using it here would mean flattening `parent` back into a
/// string and reintroducing exactly the ambiguity this module exists to avoid.
/// Use [`ElementId::named_usize`] directly for the rarer case of a repeated
/// part whose name is already unique on its own — an id built from an
/// `EntityId`, say — where there is no parent id to keep.
///
/// ```
/// use herogpui_core::element_id::{indexed, scoped};
///
/// let tabs = "settings".into();
///
/// assert_eq!(indexed(&tabs, "tab", 2).to_string(), "settings-tab-2");
/// assert_ne!(indexed(&tabs, "tab", 2), indexed(&tabs, "tab", 3));
/// // A part and the same part indexed are different ids.
/// assert_ne!(indexed(&tabs, "tab", 2), scoped(&tabs, "tab"));
/// ```