@yuneta/gobj-ui 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +324 -6
  2. package/dist/gobj-ui.cjs.js +10891 -5006
  3. package/dist/gobj-ui.es.js +10915 -5064
  4. package/index.js +45 -1
  5. package/package.json +4 -4
  6. package/src/c_g6_nodes_tree.js +128 -40
  7. package/src/c_yui_form.js +112 -53
  8. package/src/c_yui_gobj_tree_js.js +58 -36
  9. package/src/c_yui_json.css +139 -0
  10. package/src/c_yui_json.js +928 -0
  11. package/src/c_yui_json_graph.js +110 -20
  12. package/src/c_yui_map.js +18 -36
  13. package/src/c_yui_nav.js +6 -9
  14. package/src/c_yui_period.css +184 -0
  15. package/src/c_yui_period.js +1441 -0
  16. package/src/c_yui_shell.css +33 -0
  17. package/src/c_yui_shell.js +672 -111
  18. package/src/c_yui_treedb_graph.js +639 -37
  19. package/src/c_yui_treedb_schema.js +478 -0
  20. package/src/c_yui_treedb_topic_with_form.css +7 -42
  21. package/src/c_yui_treedb_topic_with_form.js +152 -103
  22. package/src/c_yui_treedb_topics.css +73 -0
  23. package/src/c_yui_treedb_topics.js +1070 -29
  24. package/src/c_yui_window.js +180 -97
  25. package/src/c_yui_window_manager.js +38 -4
  26. package/src/json_view_helpers.js +214 -0
  27. package/src/json_view_helpers.test.js +135 -0
  28. package/src/route_map_model.js +317 -0
  29. package/src/route_map_model.test.js +209 -0
  30. package/src/route_resolver.js +24 -1
  31. package/src/route_resolver.test.js +40 -1
  32. package/src/shell_modals.js +117 -39
  33. package/src/shell_route_map.css +225 -0
  34. package/src/shell_route_map.js +363 -0
  35. package/src/tabulator.css +67 -0
  36. package/src/yui_dev.js +130 -8
  37. package/src/yui_frontend_view.js +106 -0
  38. package/src/yui_icons.css +62 -0
  39. package/src/yui_inputs.css +8 -3
  40. package/src/yui_inputs.js +51 -8
  41. package/src/yui_tabulator_i18n.js +128 -0
  42. package/src/yui_theme.js +147 -0
  43. package/src/yui_time.js +666 -0
  44. package/src/yui_time.test.js +330 -0
  45. package/src/yui_toolbar.js +86 -43
package/README.md CHANGED
@@ -8,6 +8,45 @@ this line in `3.0.0` — the frozen v1 npm line still ships it.
8
8
 
9
9
  Published as `@yuneta/gobj-ui`. Built on top of [`@yuneta/gobj-js`](https://github.com/artgins/gobj-js).
10
10
 
11
+ > **Routing & navigation:** every navigable state is a URL. Before adding any
12
+ > view or navigable element, read **[`ROUTING.md`](ROUTING.md)** — the shell's
13
+ > routing contract (URL = source of truth, push/replace history, the
14
+ > position/preference/transient litmus).
15
+ >
16
+ > **BREAKING (4.0.0):** `yui_shell_navigate(shell, route)` now **pushes** a
17
+ > history entry by default; pass `{replace:true}` for a redirect / normalization
18
+ > / F5-restore — anything *code* decided rather than the user. It used to replace
19
+ > unless given `{push:true}` (still accepted, now redundant). A call left
20
+ > unmigrated only leaves a spurious Back entry; the default is the
21
+ > failure-tolerant direction, since a forgotten `{push}` silently broke Back.
22
+ > See ROUTING.md §7/§9.1.
23
+ >
24
+ > **BREAKING (4.0.0):** the legacy `__yui_main__` theme/resize service is
25
+ > **gone from v2**. Components are self-contained: the theme lives in
26
+ > `<html data-theme>` and gclasses follow it through **`src/yui_theme.js`**
27
+ > (`yui_theme_now()` / `yui_is_dark()` / `yui_watch_theme(gobj)`, all
28
+ > barrel-exported — the watcher translates the DOM mutation *and* the OS
29
+ > `prefers-color-scheme` flip into `EV_THEME`); reflow uses each component's
30
+ > own `ResizeObserver`. An app that registered a `__yui_main__` service for
31
+ > gobj-ui's benefit can delete it; do not re-add one.
32
+ >
33
+ > **BREAKING (4.0.0):** a window/modal **`title` is now an i18n KEY**,
34
+ > rendered with `data-i18n` so it re-translates on language change. Pass the
35
+ > key, never `t(key)`, and never compose data into it — the DATA half (a
36
+ > topic/service/marker name) travels in the new **`title_prefix`** attr/opt,
37
+ > shown before the title and never translated (`C_YUI_WINDOW`,
38
+ > `yui_shell_show_modal`, the dock chip). The old `title_fn`/`retitle_modal`
39
+ > hooks are removed.
40
+ >
41
+ > **BREAKING (4.0.0):** **minimize requires a window manager.**
42
+ > `C_YUI_WINDOW` paints its minimize button only when the window has a
43
+ > `manager` (`C_YUI_WINDOW_MANAGER`): minimize means "send to the dock", and
44
+ > without a manager there is nowhere to send it — so `showMin` is now **ignored**
45
+ > when there is no manager, and a manager-less window shows only
46
+ > maximize/restore + close. The self-contained **"shade"** fallback (roll up to
47
+ > the title bar in place) and its `is-shaded` CSS are **removed**; an app that
48
+ > relied on shading needs to register a manager.
49
+
11
50
  ## Two maintained lines
12
51
 
13
52
  This repository carries **two parallel lines** with different layouts and
@@ -15,15 +54,17 @@ consumers. They are independent snapshots (no shared git ancestry):
15
54
 
16
55
  | Line | Branch | Tag | Layout | Consumed by | How | Status |
17
56
  |------|--------|-----|--------|-------------|-----|--------|
18
- | **v2** | `main` | `2.0.0`+ | `src/` subdir | **wattyzer** | local `file:` dep on the yunetas submodule | active development |
19
- | **v1** | `v1` | `1.0.0` | `src/` subdir | **estadodelaire**, **hidraulia** | published npm `@yuneta/gobj-ui@^1.0.0` | frozen, maintenance-only |
57
+ | **v2** | `main` | `2.0.0`+ | `src/` subdir | **wattyzer**, **gui_agent**, **gui_treedb** | local `file:` dep on the yunetas submodule | active development |
58
+ | **v1** | `v1` | `1.0.1` | `src/` subdir | **estadodelaire**, **hidraulia** | published npm `@yuneta/gobj-ui@^1.0.1` (dist-tag `legacy`) | frozen, maintenance-only |
20
59
 
21
60
  - **v2 / `main`** is the active development line: the declarative shell
22
61
  (legacy-stack-free since `3.0.0`). It is embedded as a git submodule in **yunetas** at
23
- `kernel/js/gobj-ui`, and **wattyzer** consumes that checkout as a `file:`
24
- dependency (`@yuneta/gobj-ui` → `../../../yunetas/kernel/js/gobj-ui`),
25
- importing by package specifier (`@yuneta/gobj-ui/src/*.js`, exports map
26
- `"./src/*"`; the `index.js` barrel and the vite plugin stay at the package root).
62
+ `kernel/js/gobj-ui`, and **wattyzer** plus the in-repo JS yunos
63
+ (**`yunos/js/gui_agent`**, **`yunos/js/gui_treedb`**) consume that checkout as a
64
+ `file:` dependency (`@yuneta/gobj-ui` → `../../../kernel/js/gobj-ui` from a
65
+ yuno; `../../../yunetas/kernel/js/gobj-ui` from wattyzer), importing by package
66
+ specifier (`@yuneta/gobj-ui/src/*.js`, exports map `"./src/*"`; the `index.js`
67
+ barrel and the vite plugin stay at the package root).
27
68
  - **v1 / `v1`** is the frozen legacy-only stack (the declarative shell is not on
28
69
  this line). It is **published to npm**; estadodelaire and hidraulia depend on
29
70
  `@yuneta/gobj-ui@^1.0.0` from the registry. Land only maintenance fixes here,
@@ -58,8 +99,257 @@ npm test # vitest (v2/main only; v1 has no test target)
58
99
  tarball; v2 (wattyzer) imports source files by specifier. Rebuild `dist/` to
59
100
  validate and before publishing a v1 release.
60
101
 
102
+ ## Components
103
+
104
+ ### Site map — `yui_shell_show_route_map`
105
+
106
+ Every declarative-shell app can render its WHOLE navigation surface —
107
+ toolbar + account menu + every declared menu + live dynamic tabs + each
108
+ view's contributed sub-routes + the routes declared only in the route table —
109
+ as a printable, filterable, clickable tree (a floating `C_YUI_WINDOW`, modal
110
+ fallback) that doubles as the app's basic documentation. The current route is
111
+ marked "you are here". Wire it from an account-menu entry
112
+ (`type:"event"` → `EV_OPEN_SITEMAP`, or a deep-linkable `/sitemap` action
113
+ route with `redirect:"back"`) and call
114
+ `yui_shell_show_route_map(shell, {t})` from the handler; a second call
115
+ toggles it closed. The tree model is `yui_shell_nav_map()` /
116
+ `route_map_model.js` (pure, unit-tested). Semantics and the contributor
117
+ protocols (`yui_shell_set_sub_routes`, `yui_shell_register_event_handler`)
118
+ live in [`ROUTING.md`](ROUTING.md).
119
+
120
+ ### C_YUI_JSON — lazy JSON tree viewer
121
+
122
+ A container-agnostic viewer (like `C_YUI_PAGER`): it owns only a toolbar +
123
+ scrollable tree body and exposes a `$container` the parent mounts wherever it
124
+ wants (a `C_YUI_WINDOW` body, a `yui_shell_show_modal` card, or inline). It is
125
+ built to show **arbitrarily large** JSON, so it never assumes the whole
126
+ document fits in memory or the DOM.
127
+
128
+ **Server-driven lazy expansion.** The C kernel's `kw_collapse()` (`kwid.c`,
129
+ used by the `print-tranger` command) truncates over-limit dicts/arrays into a
130
+ sentinel — `{ "__collapsed__": { "path": …, "size": N } }` (dict) or
131
+ `[ { "__collapsed__": … } ]` (array). `C_YUI_JSON` renders each sentinel as an
132
+ expandable stub and, when the user opens it, **does not fetch anything itself**:
133
+ it publishes `EV_EXPAND_PATH {path, size}` to its subscriber. The subscriber is
134
+ the only party that knows the backend (it re-issues `print-tranger path=<path>`
135
+ with limits, or any equivalent), and hands the subtree back via
136
+ `EV_SUBTREE_LOADED {path, json}`. Only expanded containers are materialised in
137
+ the DOM, so the tree stays bounded regardless of document size. With no
138
+ sentinels present it degrades to a plain client-side collapsible tree.
139
+
140
+ **Contract:**
141
+
142
+ - Attributes: `subscriber`, `title` (i18n key, optional), `json_data` (initial
143
+ JSON, optional), `$container` (mounted by the parent).
144
+ - Input events: `EV_SET_JSON {json}` (replace the whole document; `ST_EMPTY` →
145
+ `ST_READY`), `EV_SUBTREE_LOADED {path, json}` (splice a fetched subtree),
146
+ `EV_SUBTREE_ERROR {path, error}`, plus `EV_REFRESH` / `EV_SHOW` / `EV_HIDE` /
147
+ `EV_LANGUAGE_CHANGED`.
148
+ - Output event: `EV_EXPAND_PATH {path, size}` (`EVF_OUTPUT_EVENT`) — the parent
149
+ must declare it in its own FSM (CHILD subscription model).
150
+ - Internal (DOM → FSM): `EV_TOGGLE_NODE`, `EV_EXPAND_COLLAPSED`, `EV_SEARCH`,
151
+ `EV_EXPAND_ALL`, `EV_COLLAPSE_ALL`, `EV_COPY_ALL`. Every kw carries only a
152
+ `path` string — never a DOM node or gobj.
153
+ - Paths use the kernel delimiter (backtick) and index arrays numerically, so a
154
+ path emitted by the viewer round-trips through `kw_find_path` on the backend.
155
+
156
+ **Backend note.** The Raw JSON feed is `print-tranger`, which serves the tranger
157
+ with both dict- and array-drill (via `kw_collapse()`): `c_tranger.c` for a
158
+ `C_TRANGER` service, and `C_NODE` (it holds `priv->tranger`) for a **treedb**.
159
+ A document that arrives with no `__collapsed__` sentinels is simply rendered
160
+ client-side (no lazy drill).
161
+
162
+ Logical DOM classes: `JSON_VIEWER`, `JSON_TOOLBAR`, `JSON_SEARCH`, `JSON_TREE`,
163
+ `JSON_ROW`, `JSON_KEY`, `JSON_VALUE`, `JSON_SUMMARY`, `JSON_COLLAPSED`,
164
+ `JSON_TIME`. The gclass imports its own `c_yui_json.css`.
165
+
166
+ ### C_YUI_TREEDB_SCHEMA — the treedb as a graph of topics (prototype)
167
+
168
+ A landing view that draws a treedb as a **graph of topics** — one node per
169
+ topic, one edge per hook/fkey relationship — built from the schema `descs`
170
+ **alone**: no data, no backend calls. It is the "every treedb is a graph" rule
171
+ applied to the schema itself, and an alternate landing to the topic cards. A
172
+ node click opens that topic's table through a real hash navigation, so the
173
+ graph is a navigation surface rather than a picture.
174
+
175
+ **Contract:**
176
+
177
+ - Attributes: `subscriber`, `descs` (`{topic_name: desc}`, the schema),
178
+ `node_route` (a hash-route template carrying a `{topic}` placeholder, e.g.
179
+ `#/topics/db/<sel>/{topic}` — a node click resolves it and navigates),
180
+ `system` (include the `__*__` system topics too, default `false`),
181
+ `$container` (mounted by the parent).
182
+ - Events: `EV_SHOW`, `EV_REBUILD`, `EV_THEME` (restyle — it repaints the G6
183
+ graph in place, preserving the user's zoom/pan), plus the internal
184
+ `EV_NODE_CLICK` a node click sends into the FSM.
185
+
186
+ Marked a **prototype**: it is barrel-exported and public from 4.0.0, but its
187
+ shape may still move. Renders with `@antv/g6` (no CSS of its own).
188
+
189
+ ### Frontend view — `setup_frontend_view`
190
+
191
+ `setup_frontend_view(self)` opens the **gobj tree of the app's own yuno** in a
192
+ floating `C_YUI_WINDOW` — the browser-side peer of the Developer window
193
+ (`setup_dev` / `build_dev_panel` / `apply_dev_traces` / `dev_window_was_open`,
194
+ `yui_dev.js`), and the JS answer to `view-gobj-tree` on a C yuno. Wire it to an
195
+ account-menu entry. It returns `null` when the window is already open, so the
196
+ host can use it to toggle. The tree is a **pure child of the window**, so every
197
+ teardown path (the ✕, or the host destroying the window to toggle the entry
198
+ off) takes it down too.
199
+
200
+ ### Modals — `yui_shell_show_modal` and the `before_close` veto
201
+
202
+ `yui_shell_show_modal(shell, $box, opts)` is the standard popup: pass
203
+ `{dialog:true}` for the adaptive dialog (centered card with the X top-right on
204
+ desktop, full-screen sheet with a back arrow on mobile), and the shell wires
205
+ Escape / backdrop / browser Back for you. It returns a `close()`.
206
+
207
+ **`opts.before_close`** guards the dismiss. It is consulted on every
208
+ *user-driven* close — Escape, backdrop, the X / back-arrow, browser Back — and
209
+ returning **`false` vetoes** it, so the caller can run its own flow instead (the
210
+ canonical case is an unsaved-changes prompt that closes the modal itself once
211
+ confirmed). On a vetoed browser-Back the history entry is re-armed, so Back
212
+ keeps working afterwards. With no guard a modal closes exactly as it always
213
+ did, and the returned `close()` always closes **unconditionally** — the veto is
214
+ for the user's dismiss, not for the code's.
215
+
61
216
  ## Conventions
62
217
 
218
+ ### i18n: a string must be able to CHANGE language, not just be translated once
219
+
220
+ Passing a string through `t()` is **not** enough. `refresh_language()` only
221
+ re-translates a node that **carries its key**, so anything a view composed with
222
+ `t()` at render time stays in the old language for the rest of its life. Three
223
+ shapes, and the fix for each:
224
+
225
+ | Shape | Symptom | Fix |
226
+ |---|---|---|
227
+ | Text built with `t()` | never changes language | `i18n` / `data-i18n` on the element (`["span", {i18n: "rows"}, t("rows")]`) |
228
+ | A composed string (`` `${key} · ${t(mode)}` ``) | carries no key at all | split it: the translatable halves get their own key. (Note `createElement2` **trims** text nodes — space a `·` separator with CSS, not with spaces.) |
229
+ | `title` / `aria-label` set with `t()` | tooltip stuck in the old language | `data-i18n-title` / `data-i18n-aria-label` |
230
+ | Anything a WIDGET renders (a Tabulator header, its paginator, a formatter) | drawn once; no attribute reaches it | subscribe to the shell and re-render (below) |
231
+
232
+ **The contract.** The app owns the locales: it switches its i18next and calls
233
+
234
+ ```js
235
+ yui_shell_language_changed(shell); // c_yui_shell.js
236
+ ```
237
+
238
+ which re-translates the document and publishes **`EV_LANGUAGE_CHANGED`**. Any
239
+ view that builds DOM imperatively subscribes to its shell (`yui_shell_of(gobj)`)
240
+ and re-renders in the ACTION — a language change is an OS notification like any
241
+ other, so it crosses the FSM, never a raw `i18next.on("languageChanged")`.
242
+
243
+ **Tabulator** renders its own chrome (the paginator, the placeholder, the
244
+ loading/error notices) and it never went through i18n. Use:
245
+
246
+ ```js
247
+ new Tabulator($el, {...settings, ...yui_tabulator_lang(t)}); // at build
248
+ yui_tabulator_relocalize(table, t); // on the event
249
+ ```
250
+
251
+ Every key falls back to the English string Tabulator used to render
252
+ (`defaultValue`), so an app that defines none of them sees no change. Two traps
253
+ the implementation already handles: `setLocale()` with the locale name already
254
+ in force is a **no-op** (hence a fresh name per switch), and re-applying a locale
255
+ makes Tabulator re-run a title formatter on the EXISTING header cell, which
256
+ **appends** to it — rebuild the columns from their definitions.
257
+
258
+ **A missing key is invisible:** i18next answers an unknown key **with the key
259
+ itself**, so it renders (lower-case English) and simply never changes language.
260
+ A **duplicate** key in a locale file is silent too — an object literal keeps the
261
+ last one. Both are caught by the apps' `scripts/validate-locales.mjs`, which
262
+ also scans the gobj-ui modules the app mounts: **the library translates through
263
+ the APP's i18next**, so every key it asks for must be defined by the app.
264
+
265
+ ### Dates: never hand-roll them again
266
+
267
+ Every date UI in the projects had grown its own copy of the same two things —
268
+ "epoch → the local wall clock" and "what are the bounds of this week" — and the
269
+ copies disagreed (one rendered UTC, another local; one closed a range on the
270
+ next bucket's first instant, another on its last). Both now live here, and
271
+ nothing else should.
272
+
273
+ **`yui_time.js` — the pure half** (no DOM, no dependency):
274
+
275
+ - `epoch_to_local_input` / `local_input_to_epoch` / `fmt_epoch` / `epoch_to_ms`
276
+ / `ms_to_epoch` — every conversion crosses the producer's unit flag
277
+ (`ms`: seconds unless a topic's `system_flag` says milliseconds).
278
+ - `period_bounds` / `period_shift` / `period_start` / `period_label` /
279
+ `infer_period` / `is_current_period` — the algebra of **periods**.
280
+
281
+ A period is **`(unit, count)`**, not a name from a fixed list:
282
+
283
+ ```js
284
+ {id: "quarter", unit: "month", count: 3} // and semester is count 6,
285
+ {id: "bimester", unit: "month", count: 2} // bimester 2, decade year×10,
286
+ {id: "15min", unit: "minute", count: 15} // …
287
+ ```
288
+
289
+ so an app that reports by quarter DECLARES a quarter — it does not ask for a new
290
+ component. `YUI_PERIODS` is the catalog of the named ones; anything an app
291
+ invents labels itself by its own edges (`1 jul – 31 aug 2026`).
292
+
293
+ Three invariants worth knowing before touching it:
294
+
295
+ - **Buckets are aligned**, never counted back from now: months to the year (so
296
+ 2/3/4/6/12 fall on calendar boundaries), weeks to Monday (ISO), hours to local
297
+ midnight. A window that ends at `now` is a **rolling** window (`YUI_ROLLING`),
298
+ a different animal — it has no previous, and its upper end stays **open**.
299
+ - **The upper bound is inclusive** — the bucket's last millisecond, not the next
300
+ one's first. Both ends of a match condition are inclusive, and an exclusive end
301
+ handed to one silently swallows the record that landed on the boundary.
302
+ - **Stepping is calendar arithmetic**, never `+86400000`: a DST day is 23 or 25
303
+ hours long, and `31 jan + 1 month` is february, not "3 march".
304
+
305
+ **`C_YUI_PERIOD` — the UI half**: a granularity strip + `‹ label › >|` + a
306
+ calendar on the label (day / month / year grid, chosen by the granularity's own
307
+ unit). It publishes `EV_PERIOD_CHANGED {mode, anchor, from, to}` and mirrors
308
+ `from`/`to` in read-only attrs, in the consumer's unit, `0` = unbounded. Modes
309
+ that cannot be walked (`span`, `custom`, a rolling window) live in `ST_FLAT`, so
310
+ an arrow arriving there fails loudly. `with_custom` reveals a `$custom` slot the
311
+ HOST fills (its own from/to inputs): the component shows and hides it with the
312
+ mode, the host owns what is in it. Reference consumer: the Rows options of
313
+ `gui_treedb`'s `C_TRANGER_VIEW`; live demo in `test-app` (chapter **Period**).
314
+
315
+ The library asks the APP's i18next for its keys, so a consumer must define them
316
+ (`day`, `week`, `quarter`, `today`, `week {{n}}`, `quarter {{n}} {{y}}`,
317
+ `previous period`, …) — copy the block from `test-app/src/locales.js`, which is
318
+ the complete one: it is the only consumer that declares every mode, `rolling`
319
+ included (`last 24h`, `last 7 days`), and a missing key is **invisible** —
320
+ i18next answers it with the key itself.
321
+ The picker subscribes ITSELF to the shell's `EV_LANGUAGE_CHANGED` (its labels
322
+ are composed at render time), so a host has nothing to forward — a host that
323
+ forwards the event anyway just repaints it twice, harmlessly. All Intl
324
+ formatting (month names, weekday initials, the parked-bucket label) follows
325
+ i18next's ACTIVE language, not `navigator.language` — the calendar never mixes
326
+ scripts with the UI around it.
327
+
328
+ ### Inputs: a clear (✕) is the norm on free-text fields
329
+
330
+ Every editable free-text field carries a clear button — a big help on mobile,
331
+ and `C_YUI_FORM` wires it into its field factory automatically (text / password
332
+ / url / tel and the text-backed numerics; excluded: color, datetime-local,
333
+ readonly). Build a bespoke one-off clear and it will look different from every
334
+ other one, so use the helper:
335
+
336
+ ```js
337
+ import {attach_clear, refresh_clear} from "@yuneta/gobj-ui";
338
+
339
+ attach_clear($control, $input, on_clear); // Bulma .delete inside the control
340
+ ```
341
+
342
+ `attach_clear($control, $input, on_clear)` appends a Bulma `.delete` that is
343
+ visible only while the field has content, hides itself while the input is
344
+ `readonly`/`disabled`, dispatches a **synthetic `input` event** so existing
345
+ handlers re-run on their own (which is why a component rarely needs a dedicated
346
+ "cleared" event), then refocuses. Its tooltip carries `data-i18n-title` /
347
+ `data-i18n-aria-label`, so it re-translates on a language change.
348
+
349
+ `refresh_clear($input)` re-syncs the button's visibility after a change that
350
+ fires **no** `input` event — a value loaded into the form, or `readonly` toggled
351
+ by the form mode. No-op on an input that never got a clear.
352
+
63
353
  ### Logical class names on important DOM blocks
64
354
 
65
355
  When a gclass builds DOM, tag its elements so the tree is self-describing in
@@ -88,4 +378,32 @@ renames a logical class, and adding a logical class never requires a CSS rule.
88
378
  you can't tell it's "the comment line". These are primarily debug aids, but
89
379
  they **may** double as real CSS hooks; styling them is fine when useful.
90
380
 
381
+ #### Naming a window / modal from the app: `logical_class`
382
+
383
+ The library's own chrome carries its block names — a window is tagged
384
+ `WINDOW_HEADER` / `WINDOW_CONTROLS` / `WINDOW_MIN` / `WINDOW_MAX` /
385
+ `WINDOW_CLOSE` / `WINDOW_BODY` / `WINDOW_FOOTER` / `WINDOW_RESIZE` and its
386
+ default title bar `WINDOW_TITLE` / `WINDOW_TITLE_PREFIX` / `WINDOW_TITLE_KIND`,
387
+ a modal
388
+ `MODAL` / `MODAL_BACKDROP` / `MODAL_CONTENT` / `MODAL_HEADER` / `MODAL_BACK` /
389
+ `MODAL_TITLE` (+ `MODAL_TITLE_PREFIX` / `MODAL_TITLE_KIND`) / `MODAL_CLOSE` /
390
+ `MODAL_BODY`, a confirm `CONFIRM*` and a toast
391
+ `TOAST*`.
392
+
393
+ Those names identify the *kind* of block, not the *instance*: every window in
394
+ the app is a `C_YUI_WINDOW`, every popup is a `MODAL`. To target **one**
395
+ exactly, the caller passes its own name:
396
+
397
+ ```js
398
+ gobj_create_service("keys", "C_YUI_WINDOW",
399
+ {logical_class: "TRANGER_KEYS_WINDOW", ...}, gobj);
400
+
401
+ yui_shell_show_modal(shell, $box,
402
+ {logical_class: "TRANGER_KEYS_SHEET", dialog: true, ...});
403
+
404
+ yui_shell_confirm_yesno(shell, msg, {logical_class: "...", ...});
405
+ ```
406
+
407
+ It lands on the root element, alongside `C_YUI_WINDOW` / `MODAL` / `CONFIRM`.
408
+
91
409
  Copyright (c) 2024-2026, ArtGins. All Rights Reserved.