@zeyos/zx 2.2.1 → 3.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 (117) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/DESIGN-SYSTEM.md +136 -0
  3. package/MIGRATION.md +155 -4
  4. package/README.md +51 -14
  5. package/dist/types/compat/map/data.d.ts +3 -2
  6. package/dist/types/components/account-menu/account-menu.d.ts +144 -0
  7. package/dist/types/components/app-rail/app-rail.d.ts +250 -0
  8. package/dist/types/components/app-sidebar/app-sidebar.d.ts +169 -0
  9. package/dist/types/components/avatar/avatar.d.ts +100 -0
  10. package/dist/types/components/chart/chart.d.ts +321 -0
  11. package/dist/types/components/date-picker/month-picker.d.ts +6 -0
  12. package/dist/types/components/dialog/dialog.d.ts +9 -0
  13. package/dist/types/components/dock/dock.d.ts +495 -0
  14. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  15. package/dist/types/components/field/field.d.ts +14 -0
  16. package/dist/types/components/field-upload/field-upload.d.ts +37 -4
  17. package/dist/types/components/form/form.d.ts +25 -3
  18. package/dist/types/components/grid/grid.d.ts +172 -0
  19. package/dist/types/components/launcher/launcher.d.ts +300 -0
  20. package/dist/types/components/layout/layout.d.ts +4 -4
  21. package/dist/types/components/menu-button/menu-button.d.ts +10 -2
  22. package/dist/types/components/modal/modal.d.ts +25 -0
  23. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +74 -9
  24. package/dist/types/components/questionnaire/items.d.ts +327 -0
  25. package/dist/types/components/questionnaire/questionnaire.d.ts +449 -0
  26. package/dist/types/components/select/select.d.ts +36 -1
  27. package/dist/types/components/sheet/sheet-stack.d.ts +167 -0
  28. package/dist/types/components/sheet/sheet.d.ts +294 -0
  29. package/dist/types/components/split-view/split-view.d.ts +2 -21
  30. package/dist/types/components/tabbox/tabbox.d.ts +12 -3
  31. package/dist/types/components/table/table.d.ts +219 -12
  32. package/dist/types/components/tooltip/tooltip.d.ts +2 -2
  33. package/dist/types/components/value-list/value-list.d.ts +24 -3
  34. package/dist/types/core/dom.d.ts +114 -0
  35. package/dist/types/core/drag-axis.d.ts +111 -0
  36. package/dist/types/core/position.d.ts +4 -2
  37. package/dist/types/index.d.ts +12 -2
  38. package/dist/zx-compat.esm.js +943 -311
  39. package/dist/zx-compat.esm.min.js +1 -1
  40. package/dist/zx-compat.global.js +7893 -2252
  41. package/dist/zx-compat.global.min.js +3 -3
  42. package/dist/zx-zeyos.esm.js +826 -114
  43. package/dist/zx-zeyos.esm.min.js +1 -1
  44. package/dist/zx.css +1720 -243
  45. package/dist/zx.esm.js +8227 -2599
  46. package/dist/zx.esm.min.js +3 -3
  47. package/dist/zx.global.js +8227 -2599
  48. package/dist/zx.global.min.js +3 -3
  49. package/dist/zx.min.css +1 -1
  50. package/docs/api.json +2597 -255
  51. package/docs/llms.md +475 -37
  52. package/docs/llms.txt +21 -9
  53. package/package.json +4 -2
  54. package/src/compat/globals.js +3 -1
  55. package/src/compat/map/data.js +31 -9
  56. package/src/components/account-menu/account-menu.css +70 -0
  57. package/src/components/account-menu/account-menu.js +208 -0
  58. package/src/components/app-rail/app-rail.css +107 -0
  59. package/src/components/app-rail/app-rail.js +668 -0
  60. package/src/components/app-sidebar/app-sidebar.css +68 -0
  61. package/src/components/app-sidebar/app-sidebar.js +388 -0
  62. package/src/components/avatar/avatar.css +61 -0
  63. package/src/components/avatar/avatar.js +222 -0
  64. package/src/components/button/button.css +0 -1
  65. package/src/components/chart/chart.css +61 -0
  66. package/src/components/chart/chart.js +518 -0
  67. package/src/components/date-picker/date-range-picker.css +7 -0
  68. package/src/components/date-picker/month-picker.js +9 -0
  69. package/src/components/dialog/dialog.js +11 -4
  70. package/src/components/dock/dock.css +228 -0
  71. package/src/components/dock/dock.js +977 -0
  72. package/src/components/dropdown/dropdown.js +1 -1
  73. package/src/components/field/field.js +2 -0
  74. package/src/components/field-upload/field-upload.js +9 -4
  75. package/src/components/fieldset/fieldset.css +7 -0
  76. package/src/components/form/form.css +7 -0
  77. package/src/components/form/form.js +6 -3
  78. package/src/components/grid/grid.css +6 -0
  79. package/src/components/grid/grid.js +213 -0
  80. package/src/components/launcher/launcher.css +100 -0
  81. package/src/components/launcher/launcher.js +557 -0
  82. package/src/components/layout/layout.js +2 -2
  83. package/src/components/menu-button/menu-button.js +27 -2
  84. package/src/components/modal/modal.css +5 -1
  85. package/src/components/modal/modal.js +37 -2
  86. package/src/components/multi-value-editor/multi-value-editor.css +99 -15
  87. package/src/components/multi-value-editor/multi-value-editor.js +237 -19
  88. package/src/components/navigation-bar/navigation-bar.css +7 -0
  89. package/src/components/questionnaire/items.js +370 -0
  90. package/src/components/questionnaire/questionnaire.css +387 -0
  91. package/src/components/questionnaire/questionnaire.js +1033 -0
  92. package/src/components/search/search.css +10 -2
  93. package/src/components/select/select.css +6 -1
  94. package/src/components/select/select.js +99 -11
  95. package/src/components/sheet/sheet-stack.js +304 -0
  96. package/src/components/sheet/sheet.css +351 -0
  97. package/src/components/sheet/sheet.js +551 -0
  98. package/src/components/split-view/split-view.js +76 -161
  99. package/src/components/stepper/stepper.css +12 -2
  100. package/src/components/tabbox/tabbox.css +13 -3
  101. package/src/components/tabbox/tabbox.js +34 -10
  102. package/src/components/table/table.css +43 -0
  103. package/src/components/table/table.js +523 -45
  104. package/src/components/tooltip/tooltip.js +5 -2
  105. package/src/components/value-list/value-list.js +6 -3
  106. package/src/core/dom.js +302 -0
  107. package/src/core/drag-axis.js +203 -0
  108. package/src/core/position.js +68 -22
  109. package/src/index.js +19 -2
  110. package/styles/tokens/dark.css +15 -13
  111. package/styles/tokens/global.css +17 -5
  112. package/styles/tokens/semantic.css +14 -8
  113. package/styles/tokens/themes.css +72 -0
  114. package/styles/zx.css +11 -1
  115. package/dist/types/components/permission/permission.d.ts +0 -93
  116. package/src/components/permission/permission.css +0 -47
  117. package/src/components/permission/permission.js +0 -198
@@ -0,0 +1,449 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { Field } from '../field/field.js';
3
+ import { ProgressBar } from '../loading/progress-bar.js';
4
+ export type QuestionnaireItem = import('./items.js').QuestionnaireItem;
5
+ export type QuestionnaireChoice = import('./items.js').QuestionnaireChoice;
6
+ export type NormalizedItem = import('./items.js').NormalizedItem;
7
+ export type QuestionnaireProgress = import('./items.js').QuestionnaireProgress;
8
+ export type QuestionnaireAdvance = 'manual' | 'auto';
9
+ export type QuestionnaireStatus = 'asking' | 'review' | 'complete';
10
+ export type QuestionnaireOptions = {
11
+ /**
12
+ * Ordered question definitions.
13
+ */
14
+ items?: QuestionnaireItem[];
15
+ /**
16
+ * Answers to start from. Together with `active`
17
+ * this is the resume pair: hand back what a previous session collected.
18
+ */
19
+ answers?: Record<string, unknown>;
20
+ /**
21
+ * Item to open, defaulting to the first visible one.
22
+ */
23
+ active?: string | null;
24
+ /**
25
+ * Whether the progress bar and counter are shown.
26
+ */
27
+ progress?: boolean;
28
+ /**
29
+ * Whether a summary of every answer is shown before submitting.
30
+ */
31
+ review?: boolean;
32
+ /**
33
+ * `auto` moves on by itself once a
34
+ * single-choice question is answered.
35
+ */
36
+ advance?: QuestionnaireAdvance;
37
+ /**
38
+ * Whether `1`–`9` and `a`–`z` pick an answer.
39
+ */
40
+ shortcuts?: boolean;
41
+ /**
42
+ * Whether the Back action is offered.
43
+ */
44
+ allowBack?: boolean;
45
+ /**
46
+ * Localized messages.
47
+ */
48
+ msg?: Record<string, string> | Record<string, Record<string, string>>;
49
+ /**
50
+ * Answer listener.
51
+ */
52
+ onchange?: (event: CustomEvent<QuestionnaireChangeDetail>) => void;
53
+ /**
54
+ * Navigation listener.
55
+ */
56
+ onnavigate?: (event: CustomEvent<QuestionnaireNavigateDetail>) => void;
57
+ /**
58
+ * Skip listener.
59
+ */
60
+ onskip?: (event: CustomEvent<QuestionnaireSkipDetail>) => void;
61
+ /**
62
+ * Validation listener.
63
+ */
64
+ oninvalid?: (event: CustomEvent<QuestionnaireInvalidDetail>) => void;
65
+ /**
66
+ * Submit listener.
67
+ */
68
+ onsubmit?: (event: CustomEvent<QuestionnaireSubmitDetail>) => void;
69
+ /**
70
+ * Completion listener.
71
+ */
72
+ oncomplete?: (event: CustomEvent<QuestionnaireCompleteDetail>) => void;
73
+ };
74
+ export type QuestionnaireChangeDetail = {
75
+ /**
76
+ * Item whose answer changed.
77
+ */
78
+ name: string;
79
+ /**
80
+ * The new answer.
81
+ */
82
+ value: unknown;
83
+ /**
84
+ * Every visible answer after the change.
85
+ */
86
+ answers: Record<string, unknown>;
87
+ };
88
+ export type QuestionnaireNavigateDetail = {
89
+ /**
90
+ * Item being left, or null.
91
+ */
92
+ from: string | null;
93
+ /**
94
+ * Item being opened, or null when the review screen is being entered.
95
+ */
96
+ to: string | null;
97
+ /**
98
+ * What triggered the move.
99
+ */
100
+ reason: 'next' | 'previous' | 'skip' | 'goto' | 'review';
101
+ };
102
+ export type QuestionnaireSkipDetail = {
103
+ name: string;
104
+ };
105
+ export type QuestionnaireInvalidDetail = {
106
+ name: string;
107
+ message: string;
108
+ };
109
+ export type QuestionnaireSubmitDetail = {
110
+ answers: Record<string, unknown>;
111
+ path: string[];
112
+ };
113
+ export type QuestionnaireCompleteDetail = {
114
+ answers: Record<string, unknown>;
115
+ path: string[];
116
+ };
117
+ /**
118
+ * @typedef {Object} QuestionnaireOptions
119
+ * @property {QuestionnaireItem[]} [items=[]] Ordered question definitions.
120
+ * @property {Record<string, unknown>} [answers={}] Answers to start from. Together with `active`
121
+ * this is the resume pair: hand back what a previous session collected.
122
+ * @property {string|null} [active=null] Item to open, defaulting to the first visible one.
123
+ * @property {boolean} [progress=true] Whether the progress bar and counter are shown.
124
+ * @property {boolean} [review=false] Whether a summary of every answer is shown before submitting.
125
+ * @property {QuestionnaireAdvance} [advance='manual'] `auto` moves on by itself once a
126
+ * single-choice question is answered.
127
+ * @property {boolean} [shortcuts=true] Whether `1`–`9` and `a`–`z` pick an answer.
128
+ * @property {boolean} [allowBack=true] Whether the Back action is offered.
129
+ * @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
130
+ * @property {(event: CustomEvent<QuestionnaireChangeDetail>) => void} [onchange] Answer listener.
131
+ * @property {(event: CustomEvent<QuestionnaireNavigateDetail>) => void} [onnavigate] Navigation listener.
132
+ * @property {(event: CustomEvent<QuestionnaireSkipDetail>) => void} [onskip] Skip listener.
133
+ * @property {(event: CustomEvent<QuestionnaireInvalidDetail>) => void} [oninvalid] Validation listener.
134
+ * @property {(event: CustomEvent<QuestionnaireSubmitDetail>) => void} [onsubmit] Submit listener.
135
+ * @property {(event: CustomEvent<QuestionnaireCompleteDetail>) => void} [oncomplete] Completion listener.
136
+ */
137
+ /**
138
+ * @typedef {Object} QuestionnaireChangeDetail
139
+ * @property {string} name Item whose answer changed.
140
+ * @property {unknown} value The new answer.
141
+ * @property {Record<string, unknown>} answers Every visible answer after the change.
142
+ */
143
+ /**
144
+ * @typedef {Object} QuestionnaireNavigateDetail
145
+ * @property {string|null} from Item being left, or null.
146
+ * @property {string|null} to Item being opened, or null when the review screen is being entered.
147
+ * @property {'next'|'previous'|'skip'|'goto'|'review'} reason What triggered the move.
148
+ */
149
+ /** @typedef {{name: string}} QuestionnaireSkipDetail */
150
+ /** @typedef {{name: string, message: string}} QuestionnaireInvalidDetail */
151
+ /** @typedef {{answers: Record<string, unknown>, path: string[]}} QuestionnaireSubmitDetail */
152
+ /** @typedef {{answers: Record<string, unknown>, path: string[]}} QuestionnaireCompleteDetail */
153
+ /**
154
+ * A guided, one-question-at-a-time flow: onboarding intake, a service-call checklist, an audit
155
+ * form, a feedback survey. The questionnaire owns the ordered items, the active one, the answers,
156
+ * validation, progress and navigation; the page, card, dialog or drawer around it owns closing,
157
+ * persistence and transport.
158
+ *
159
+ * Three things separate it from a `Form` with one `Fieldset` per question. It **branches**: an item
160
+ * carries a `when` predicate and a `next` target, conditions cascade, and Back retraces the path
161
+ * actually walked rather than an array index. Its answer control is **any registered Zx `Field`
162
+ * type**, so a question can ask for a date, a number, a rating or an upload instead of a choice.
163
+ * And its `validate` may be asynchronous, which is what lets a question be gated on a server check.
164
+ *
165
+ * The root is a real `<form>` and choices are native radios and checkboxes carrying the item name,
166
+ * so `new FormData(questionnaire.toElement())` reads the answers, arrow-key navigation inside a
167
+ * choice group is the browser's rather than ours, and nothing depends on JavaScript for semantics.
168
+ *
169
+ * @fires Questionnaire#change
170
+ * @fires Questionnaire#navigate
171
+ * @fires Questionnaire#skip
172
+ * @fires Questionnaire#invalid
173
+ * @fires Questionnaire#submit
174
+ * @fires Questionnaire#complete
175
+ * @extends {Component<QuestionnaireOptions>}
176
+ */
177
+ export declare class Questionnaire extends Component<QuestionnaireOptions> {
178
+ _createdRoot: boolean;
179
+ _snapshot: import("../../core/dom.js").TargetSnapshot;
180
+ _destroyed: boolean;
181
+ /** @type {NormalizedItem[]} */
182
+ _items: NormalizedItem[];
183
+ /** @type {Map<string, Array<string|number>>} */
184
+ _selected: Map<string, Array<string | number>>;
185
+ /** @type {Map<string, string>} */
186
+ _freeform: Map<string, string>;
187
+ /** @type {Map<string, Field>} */
188
+ _fields: Map<string, Field>;
189
+ /** @type {string[]} */
190
+ _path: string[];
191
+ /** @type {QuestionnaireStatus} */
192
+ _status: QuestionnaireStatus;
193
+ _returnToReview: any;
194
+ _busy: boolean;
195
+ _error: string;
196
+ _advanceTimer: number;
197
+ _uid: string;
198
+ _form: HTMLElement;
199
+ _progressHost: HTMLElement;
200
+ _itemHost: HTMLElement;
201
+ _reviewHost: HTMLElement;
202
+ _actionsHost: HTMLElement;
203
+ _bar: ProgressBar;
204
+ _back: HTMLButtonElement;
205
+ _skip: HTMLButtonElement;
206
+ _forward: HTMLButtonElement;
207
+ static cssName: string;
208
+ /** @type {QuestionnaireOptions & {msg: Record<string, string>}} */
209
+ static defaults: QuestionnaireOptions & {
210
+ msg: Record<string, string>;
211
+ };
212
+ /**
213
+ * Builds the questionnaire. Runs inside the base constructor, so every piece of instance state
214
+ * is initialized here rather than in class fields.
215
+ * @returns {HTMLElement}
216
+ */
217
+ render(): HTMLElement;
218
+ /**
219
+ * Replaces the whole item list. Answers survive for items that are still present, and so does
220
+ * the active item; when it is gone the first visible item takes over. This is a structural
221
+ * change, so it never emits `navigate`.
222
+ * @param {QuestionnaireItem[]} list Item definitions.
223
+ * @returns {this}
224
+ * @throws {TypeError} When the list, an item, or a name is malformed, or a name repeats.
225
+ */
226
+ setItems(list: QuestionnaireItem[]): this;
227
+ /**
228
+ * Returns the normalized items, including ones currently conditioned out.
229
+ * @returns {NormalizedItem[]}
230
+ */
231
+ getItems(): NormalizedItem[];
232
+ /**
233
+ * Opens an item without validating the one being left — what the review screen's Edit action
234
+ * does. An item already on the path truncates it, so Back keeps working; a new one is appended.
235
+ * @param {string} name Item name.
236
+ * @returns {boolean} False when the move was vetoed or the item is conditioned out.
237
+ * @throws {RangeError} When no item carries that name.
238
+ * @fires Questionnaire#navigate
239
+ */
240
+ goTo(name: string): boolean;
241
+ /**
242
+ * Validates the active item and moves on: to the branch target, then the review screen, then
243
+ * submission. Returns a promise because an item's `validate` may be asynchronous.
244
+ * @returns {Promise<boolean>} False when validation failed or the move was vetoed.
245
+ * @fires Questionnaire#navigate
246
+ * @fires Questionnaire#invalid
247
+ */
248
+ next(): Promise<boolean>;
249
+ /**
250
+ * Steps back along the path actually walked — which is not `index - 1` once a branch has been
251
+ * taken. From the review screen it reopens the last question.
252
+ * @returns {boolean} False at the start of the questionnaire, or when the move was vetoed.
253
+ * @fires Questionnaire#navigate
254
+ */
255
+ previous(): boolean;
256
+ /**
257
+ * Clears the active item's answer and moves on without validating it. Skipping the last
258
+ * question submits, so this is a promise for the same reason `submit()` is.
259
+ * @returns {Promise<boolean>} False when the item is not skippable or the move was vetoed.
260
+ * @fires Questionnaire#skip
261
+ * @fires Questionnaire#navigate
262
+ */
263
+ skip(): Promise<boolean>;
264
+ /**
265
+ * Validates the active item (unless already on the review screen) and submits.
266
+ * @returns {Promise<boolean>} False when validation failed or `submit` was prevented.
267
+ * @fires Questionnaire#submit
268
+ * @fires Questionnaire#complete
269
+ */
270
+ submit(): Promise<boolean>;
271
+ /**
272
+ * Returns the answers of every visible question that carries one, in declaration order.
273
+ * Questions conditioned out are left out, so an abandoned branch never submits stale data.
274
+ * @returns {Record<string, unknown>}
275
+ */
276
+ getAnswers(): Record<string, unknown>;
277
+ /**
278
+ * Returns one answer, whether or not its question is currently visible.
279
+ * @param {string} name Item name.
280
+ * @returns {unknown}
281
+ */
282
+ getAnswer(name: string): unknown;
283
+ /**
284
+ * Sets several answers at once.
285
+ * @param {Record<string, unknown>} values Answers keyed by item name.
286
+ * @param {{silent?: boolean}} [options={}] Set `silent` to suppress `change`.
287
+ * @returns {this}
288
+ * @fires Questionnaire#change
289
+ */
290
+ setAnswers(values: Record<string, unknown>, { silent }?: {
291
+ silent?: boolean;
292
+ }): this;
293
+ /**
294
+ * Sets one answer. A value matching a choice selects it; anything else lands in the freeform
295
+ * input when the item has one.
296
+ * @param {string} name Item name.
297
+ * @param {unknown} value New answer, or null to clear it.
298
+ * @param {{silent?: boolean}} [options={}] Set `silent` to suppress `change`.
299
+ * @returns {this}
300
+ * @throws {RangeError} When no item carries that name.
301
+ * @fires Questionnaire#change
302
+ */
303
+ setAnswer(name: string, value: unknown, { silent }?: {
304
+ silent?: boolean;
305
+ }): this;
306
+ /**
307
+ * Returns the active item name, or null on the review or completion screen.
308
+ * @returns {string|null}
309
+ */
310
+ getActive(): string | null;
311
+ /**
312
+ * Returns the item names visited so far, active last. This is the questionnaire's memory of the
313
+ * route through the branches.
314
+ * @returns {string[]}
315
+ */
316
+ getPath(): string[];
317
+ /**
318
+ * Returns progress along the path walked. `total` is exact while the flow is static and an
319
+ * estimate once a function `next` can jump somewhere the forward walk cannot predict.
320
+ * @returns {QuestionnaireProgress}
321
+ */
322
+ getProgress(): QuestionnaireProgress;
323
+ /**
324
+ * Returns everything a caller needs to resume this questionnaire later.
325
+ * @returns {{active: string|null, status: QuestionnaireStatus, answers: Record<string, unknown>, path: string[], progress: QuestionnaireProgress}}
326
+ */
327
+ getState(): {
328
+ active: string | null;
329
+ status: QuestionnaireStatus;
330
+ answers: Record<string, unknown>;
331
+ path: string[];
332
+ progress: QuestionnaireProgress;
333
+ };
334
+ /**
335
+ * Returns the visible answers as form data, with one entry per value of a multiple-choice item —
336
+ * the same shape `new FormData(questionnaire.toElement())` produces.
337
+ * @returns {FormData}
338
+ */
339
+ toFormData(): FormData;
340
+ /**
341
+ * Clears every answer and returns to the first visible question.
342
+ * @returns {this}
343
+ */
344
+ reset(): this;
345
+ /**
346
+ * Moves focus to the active question, which is what a screen reader announces after a move.
347
+ * @returns {this}
348
+ */
349
+ focus(): this;
350
+ /** @returns {void} */
351
+ destroy(): void;
352
+ /** @returns {NormalizedItem|null} */
353
+ _activeItem(): NormalizedItem | null;
354
+ /**
355
+ * Every answer given, including ones on branches that are currently conditioned out — walking
356
+ * back into a branch restores what was typed there. `resolveFlow()` filters this down.
357
+ * @returns {Record<string, unknown>}
358
+ */
359
+ _rawAnswers(): Record<string, unknown>;
360
+ /** @param {NormalizedItem} item @returns {unknown} */
361
+ _answerOf(item: NormalizedItem): unknown;
362
+ /**
363
+ * Returns the item's Field, building it on first use. Fields are cached rather than rebuilt per
364
+ * paint, so what a reader typed survives walking away from the question and back.
365
+ * @param {NormalizedItem} item Item carrying `field` options.
366
+ * @returns {Field}
367
+ */
368
+ _field(item: NormalizedItem): Field;
369
+ /** @param {string} name @param {QuestionnaireNavigateDetail['reason']} reason @returns {boolean} */
370
+ _navigate(name: string, reason: QuestionnaireNavigateDetail['reason']): boolean;
371
+ /** @returns {boolean} */
372
+ _enterReview(): boolean;
373
+ /** @returns {Promise<boolean>} */
374
+ _validateActive(): Promise<boolean>;
375
+ /** @param {boolean} busy @returns {void} */
376
+ _setBusy(busy: boolean): void;
377
+ /** @returns {void} */
378
+ _paint(): void;
379
+ /** @returns {void} */
380
+ _syncState(): void;
381
+ /** @returns {void} */
382
+ _paintProgress(): void;
383
+ /** @returns {void} */
384
+ _paintItem(): void;
385
+ /** @param {NormalizedItem} item @returns {HTMLElement} */
386
+ _choices(item: NormalizedItem): HTMLElement;
387
+ /** @param {NormalizedItem} item @returns {void} */
388
+ _paintError(item: NormalizedItem): void;
389
+ /** @returns {void} */
390
+ _paintReview(): void;
391
+ /** @param {NormalizedItem} item @param {unknown} value @returns {string} */
392
+ _formatAnswer(item: NormalizedItem, value: unknown): string;
393
+ /** @returns {void} */
394
+ _paintActions(): void;
395
+ /**
396
+ * @param {string} action @param {string} label @param {string|null} glyph
397
+ * @param {'default'|'primary'|'ghost'} kind @returns {HTMLButtonElement}
398
+ */
399
+ _action(action: string, label: string, glyph: string | null, kind: 'default' | 'primary' | 'ghost'): HTMLButtonElement;
400
+ /** @param {Event} event @returns {void} */
401
+ _onControlChange(event: Event): void;
402
+ /** @param {Event} event @returns {void} */
403
+ _onFreeformInput(event: Event): void;
404
+ /** @param {Event} event @returns {void} */
405
+ _onActionClick(event: Event): void;
406
+ /** @param {Event} event @returns {void} */
407
+ _onReviewClick(event: Event): void;
408
+ /** @param {KeyboardEvent} event @returns {void} */
409
+ _onKeydown(event: KeyboardEvent): void;
410
+ /** @param {NormalizedItem} item @returns {void} */
411
+ _maybeAutoAdvance(item: NormalizedItem): void;
412
+ /** @param {NormalizedItem} item @returns {void} */
413
+ _emitChange(item: NormalizedItem): void;
414
+ /** @returns {void} */
415
+ _focusItem(): void;
416
+ /** @returns {void} */
417
+ _focusControl(): void;
418
+ }
419
+ /**
420
+ * An answer changed.
421
+ * @event Questionnaire#change
422
+ * @type {CustomEvent<QuestionnaireChangeDetail>}
423
+ */
424
+ /**
425
+ * A move between questions, into the review screen, or back. Preventable: call
426
+ * `event.preventDefault()` to refuse the move.
427
+ * @event Questionnaire#navigate
428
+ * @type {CustomEvent<QuestionnaireNavigateDetail>}
429
+ */
430
+ /**
431
+ * A skippable question was skipped, clearing its answer.
432
+ * @event Questionnaire#skip
433
+ * @type {CustomEvent<QuestionnaireSkipDetail>}
434
+ */
435
+ /**
436
+ * Validation of the active question failed.
437
+ * @event Questionnaire#invalid
438
+ * @type {CustomEvent<QuestionnaireInvalidDetail>}
439
+ */
440
+ /**
441
+ * The questionnaire is being submitted. Preventable.
442
+ * @event Questionnaire#submit
443
+ * @type {CustomEvent<QuestionnaireSubmitDetail>}
444
+ */
445
+ /**
446
+ * Submission was not prevented and the questionnaire is finished.
447
+ * @event Questionnaire#complete
448
+ * @type {CustomEvent<QuestionnaireCompleteDetail>}
449
+ */
@@ -8,6 +8,10 @@ export type SelectOptions = {
8
8
  * Available values.
9
9
  */
10
10
  items?: SelectItem[];
11
+ /**
12
+ * Choices pinned above the list, kept through filtering and async loads.
13
+ */
14
+ fixedItems?: SelectItem[];
11
15
  /**
12
16
  * Item ID property or reader.
13
17
  */
@@ -120,7 +124,9 @@ export type SelectSetOptions = {
120
124
  export declare class Select extends Component<SelectOptions> {
121
125
  _items: any[] | SelectItem[];
122
126
  _visibleItems: any[];
123
- _selected: any;
127
+ _fixedItems: any[] | SelectItem[];
128
+ _visibleFixed: any[] | SelectItem[];
129
+ _selected: SelectItem;
124
130
  _optionEntries: any[];
125
131
  _activeIndex: number;
126
132
  _open: boolean;
@@ -159,6 +165,12 @@ export declare class Select extends Component<SelectOptions> {
159
165
  * @returns {this}
160
166
  */
161
167
  setItems(items: SelectItem[]): this;
168
+ /**
169
+ * Replaces the pinned choices and preserves selection by ID when possible.
170
+ * @param {SelectItem[]} items New fixed items.
171
+ * @returns {this}
172
+ */
173
+ setFixedItems(items: SelectItem[]): this;
162
174
  /**
163
175
  * Restores the configured initial value.
164
176
  * @param {{silent?: boolean}} [options={}] Reset behavior.
@@ -184,6 +196,20 @@ export declare class Select extends Component<SelectOptions> {
184
196
  * @returns {Select}
185
197
  */
186
198
  static priority(target: Element | string | null, options?: SelectOptions): Select;
199
+ /**
200
+ * Creates the record-permission picker: the fixed Private and Public choices above the groups a
201
+ * record can be shared with. The value is the tri-state ZeyOS stores — `false` for private,
202
+ * `true` for public, or a group ID — so the control binds straight to the record field.
203
+ *
204
+ * @param {Element|string|null} target Component target.
205
+ * @param {SelectOptions & {groups?: SelectItem[]|((query: string) => Promise<SelectItem[]>|SelectItem[])}} [options={}]
206
+ * Select overrides other than the fixed choices; `groups` holds the shareable groups, either as
207
+ * an array (filtered locally) or as a loader function (queried remotely).
208
+ * @returns {Select}
209
+ */
210
+ static permission(target: Element | string | null, options?: SelectOptions & {
211
+ groups?: SelectItem[] | ((query: string) => Promise<SelectItem[]> | SelectItem[]);
212
+ }): Select;
187
213
  /** Cleans up the panel, pending async work, and enhanced target contents. @returns {void} */
188
214
  destroy(): void;
189
215
  /** @param {KeyboardEvent} event @returns {void} */
@@ -224,6 +250,15 @@ export declare class Select extends Component<SelectOptions> {
224
250
  _itemLabel(item: SelectItem | null): string;
225
251
  /** @param {SelectItem|null} item @param {SelectValueReader|SelectLabelReader} reader @returns {unknown} */
226
252
  _read(item: SelectItem | null, reader: SelectValueReader | SelectLabelReader): unknown;
253
+ /** @returns {Array<SelectValueReader|SelectLabelReader>} */
254
+ _searchKeys(): Array<SelectValueReader | SelectLabelReader>;
255
+ /**
256
+ * Narrows the pinned choices. They are always matched here and never handed to a filter
257
+ * function: a fixed choice belongs to the control, not to whatever the remote source returns.
258
+ * @param {string} query Search text.
259
+ * @returns {SelectItem[]}
260
+ */
261
+ _matchFixed(query: string): SelectItem[];
227
262
  /** @returns {number} */
228
263
  _selectedEntryIndex(): number;
229
264
  /** @param {number} itemIndex @returns {number} */
@@ -0,0 +1,167 @@
1
+ /** @typedef {import('./sheet.js').Sheet} Sheet */
2
+ export type Sheet = import('./sheet.js').Sheet;
3
+ export type SheetStackOptions = {
4
+ /**
5
+ * How depth is expressed. `stack` slides each
6
+ * covered sheet back toward its edge and scales it down, leaving only the top one usable —
7
+ * the drill-down feel. `cascade` shifts each covered sheet clear of the ones in front so they
8
+ * sit side by side and all stay usable, which is what an ERP screen usually wants: the parent
9
+ * record stays readable while a line item is edited.
10
+ */
11
+ layout?: 'stack' | 'cascade';
12
+ /**
13
+ * Pixels a `stack` layout moves per depth step.
14
+ */
15
+ offset?: number;
16
+ /**
17
+ * How much a `stack` layout shrinks per depth step, as a fraction.
18
+ */
19
+ scale?: number;
20
+ /**
21
+ * Depth at which a sheet stops being drawn. Deeper sheets stay open and
22
+ * in the stack; they are simply no longer visible behind the ones in front.
23
+ */
24
+ max?: number;
25
+ /**
26
+ * Push listener.
27
+ */
28
+ onpush?: (event: CustomEvent<{
29
+ sheet: Sheet;
30
+ depth: number;
31
+ }>) => void;
32
+ /**
33
+ * Pop listener.
34
+ */
35
+ onpop?: (event: CustomEvent<{
36
+ sheet: Sheet;
37
+ }>) => void;
38
+ };
39
+ /**
40
+ * @typedef {Object} SheetStackOptions
41
+ * @property {'stack'|'cascade'} [layout='stack'] How depth is expressed. `stack` slides each
42
+ * covered sheet back toward its edge and scales it down, leaving only the top one usable —
43
+ * the drill-down feel. `cascade` shifts each covered sheet clear of the ones in front so they
44
+ * sit side by side and all stay usable, which is what an ERP screen usually wants: the parent
45
+ * record stays readable while a line item is edited.
46
+ * @property {number} [offset=24] Pixels a `stack` layout moves per depth step.
47
+ * @property {number} [scale=0.04] How much a `stack` layout shrinks per depth step, as a fraction.
48
+ * @property {number} [max=3] Depth at which a sheet stops being drawn. Deeper sheets stay open and
49
+ * in the stack; they are simply no longer visible behind the ones in front.
50
+ * @property {(event: CustomEvent<{sheet: Sheet, depth: number}>) => void} [onpush] Push listener.
51
+ * @property {(event: CustomEvent<{sheet: Sheet}>) => void} [onpop] Pop listener.
52
+ */
53
+ /**
54
+ * An ordered group of Sheets that read as one drill-down.
55
+ *
56
+ * Deliberately tiny, because the browser already does the hard part: nested dialogs stack in the
57
+ * top layer by open order, so there is no z-index bookkeeping here, and Escape closes only the
58
+ * topmost, so unwinding one level at a time is native behaviour rather than something to
59
+ * implement. What is left is bookkeeping — which sheet is at which depth — written onto the
60
+ * elements as `data-depth` and `--zx-sheet-depth` for CSS to interpret.
61
+ *
62
+ * Owns no element of its own, so it is a controller rather than a `Component`: there is nothing
63
+ * to render, only sheets to arrange.
64
+ *
65
+ * @fires SheetStack#push
66
+ * @fires SheetStack#pop
67
+ */
68
+ export declare class SheetStack extends EventTarget {
69
+ #private;
70
+ options: Readonly<{
71
+ layout?: 'stack' | 'cascade';
72
+ offset?: number;
73
+ scale?: number;
74
+ max?: number;
75
+ onpush?: (event: CustomEvent<{
76
+ sheet: Sheet;
77
+ depth: number;
78
+ }>) => void;
79
+ onpop?: (event: CustomEvent<{
80
+ sheet: Sheet;
81
+ }>) => void;
82
+ }>;
83
+ /** @type {Readonly<SheetStackOptions>} */
84
+ static defaults: Readonly<SheetStackOptions>;
85
+ /**
86
+ * Creates a sheet stack.
87
+ * @param {SheetStackOptions} [options={}] Stack options.
88
+ */
89
+ constructor(options?: SheetStackOptions);
90
+ /**
91
+ * Adds a sheet to the top of the stack, opening it if it is closed. A sheet already in the stack
92
+ * is raised rather than added twice.
93
+ * @param {Sheet} sheet Sheet to push.
94
+ * @returns {this}
95
+ * @fires SheetStack#push
96
+ */
97
+ push(sheet: Sheet): this;
98
+ /**
99
+ * Closes the top sheet and removes it from the stack.
100
+ *
101
+ * The removal is synchronous while the close is not — a dialog dispatches `close` in a queued
102
+ * task — so the stack updates itself here rather than waiting for the event. The close listener
103
+ * is left to handle the other direction: a sheet dismissed by Escape or its own button.
104
+ * @returns {Sheet|null} The sheet that was closed, or null on an empty stack.
105
+ * @fires SheetStack#pop
106
+ */
107
+ pop(): Sheet | null;
108
+ /**
109
+ * Closes everything above a sheet, leaving it on top.
110
+ * @param {Sheet} sheet Sheet to return to.
111
+ * @returns {this}
112
+ */
113
+ popTo(sheet: Sheet): this;
114
+ /**
115
+ * Closes every sheet, from the top down.
116
+ * @returns {this}
117
+ */
118
+ clear(): this;
119
+ /**
120
+ * The sheet currently on top.
121
+ * @returns {Sheet|null}
122
+ */
123
+ top(): Sheet | null;
124
+ /**
125
+ * How many sheets the stack holds.
126
+ * @returns {number}
127
+ */
128
+ size(): number;
129
+ /**
130
+ * The sheets, bottom first.
131
+ * @returns {Sheet[]}
132
+ */
133
+ sheets(): Sheet[];
134
+ /**
135
+ * Whether a sheet is in this stack.
136
+ * @param {Sheet} sheet Sheet to look for.
137
+ * @returns {boolean}
138
+ */
139
+ has(sheet: Sheet): boolean;
140
+ /**
141
+ * Subscribes to a stack event.
142
+ * @param {string} type Event name.
143
+ * @param {(event: any) => void} fn Listener.
144
+ * @returns {this}
145
+ */
146
+ on(type: string, fn: (event: any) => void): this;
147
+ /**
148
+ * Removes a stack event listener.
149
+ * @param {string} type Event name.
150
+ * @param {(event: any) => void} fn Listener.
151
+ * @returns {this}
152
+ */
153
+ off(type: string, fn: (event: any) => void): this;
154
+ /**
155
+ * Subscribes to one occurrence of a stack event.
156
+ * @param {string} type Event name.
157
+ * @param {(event: any) => void} fn Listener.
158
+ * @returns {this}
159
+ */
160
+ once(type: string, fn: (event: any) => void): this;
161
+ /**
162
+ * Empties the stack without closing anything. The sheets are not the stack's to destroy, so
163
+ * they are simply released with their depth styling removed.
164
+ * @returns {void}
165
+ */
166
+ destroy(): void;
167
+ }