@llui/components 0.0.1

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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/dist/components/accordion.d.ts +115 -0
  4. package/dist/components/accordion.d.ts.map +1 -0
  5. package/dist/components/accordion.js +138 -0
  6. package/dist/components/alert-dialog.d.ts +45 -0
  7. package/dist/components/alert-dialog.d.ts.map +1 -0
  8. package/dist/components/alert-dialog.js +12 -0
  9. package/dist/components/angle-slider.d.ts +121 -0
  10. package/dist/components/angle-slider.d.ts.map +1 -0
  11. package/dist/components/angle-slider.js +145 -0
  12. package/dist/components/async-list.d.ts +104 -0
  13. package/dist/components/async-list.d.ts.map +1 -0
  14. package/dist/components/async-list.js +117 -0
  15. package/dist/components/avatar.d.ts +58 -0
  16. package/dist/components/avatar.d.ts.map +1 -0
  17. package/dist/components/avatar.js +43 -0
  18. package/dist/components/carousel.d.ts +128 -0
  19. package/dist/components/carousel.d.ts.map +1 -0
  20. package/dist/components/carousel.js +131 -0
  21. package/dist/components/cascade-select.d.ts +95 -0
  22. package/dist/components/cascade-select.d.ts.map +1 -0
  23. package/dist/components/cascade-select.js +100 -0
  24. package/dist/components/checkbox.d.ts +74 -0
  25. package/dist/components/checkbox.d.ts.map +1 -0
  26. package/dist/components/checkbox.js +73 -0
  27. package/dist/components/clipboard.d.ts +72 -0
  28. package/dist/components/clipboard.d.ts.map +1 -0
  29. package/dist/components/clipboard.js +73 -0
  30. package/dist/components/collapsible.d.ts +64 -0
  31. package/dist/components/collapsible.d.ts.map +1 -0
  32. package/dist/components/collapsible.js +51 -0
  33. package/dist/components/color-picker.d.ts +125 -0
  34. package/dist/components/color-picker.d.ts.map +1 -0
  35. package/dist/components/color-picker.js +169 -0
  36. package/dist/components/combobox.d.ts +163 -0
  37. package/dist/components/combobox.d.ts.map +1 -0
  38. package/dist/components/combobox.js +345 -0
  39. package/dist/components/context-menu.d.ts +105 -0
  40. package/dist/components/context-menu.d.ts.map +1 -0
  41. package/dist/components/context-menu.js +177 -0
  42. package/dist/components/date-input.d.ts +117 -0
  43. package/dist/components/date-input.d.ts.map +1 -0
  44. package/dist/components/date-input.js +149 -0
  45. package/dist/components/date-picker.d.ts +142 -0
  46. package/dist/components/date-picker.d.ts.map +1 -0
  47. package/dist/components/date-picker.js +294 -0
  48. package/dist/components/dialog.d.ts +152 -0
  49. package/dist/components/dialog.d.ts.map +1 -0
  50. package/dist/components/dialog.js +140 -0
  51. package/dist/components/drawer.d.ts +106 -0
  52. package/dist/components/drawer.d.ts.map +1 -0
  53. package/dist/components/drawer.js +136 -0
  54. package/dist/components/editable.d.ts +92 -0
  55. package/dist/components/editable.d.ts.map +1 -0
  56. package/dist/components/editable.js +112 -0
  57. package/dist/components/file-upload.d.ts +251 -0
  58. package/dist/components/file-upload.d.ts.map +1 -0
  59. package/dist/components/file-upload.js +324 -0
  60. package/dist/components/floating-panel.d.ts +171 -0
  61. package/dist/components/floating-panel.d.ts.map +1 -0
  62. package/dist/components/floating-panel.js +198 -0
  63. package/dist/components/hover-card.d.ts +85 -0
  64. package/dist/components/hover-card.d.ts.map +1 -0
  65. package/dist/components/hover-card.js +128 -0
  66. package/dist/components/image-cropper.d.ts +129 -0
  67. package/dist/components/image-cropper.d.ts.map +1 -0
  68. package/dist/components/image-cropper.js +208 -0
  69. package/dist/components/index.d.ts +109 -0
  70. package/dist/components/index.d.ts.map +1 -0
  71. package/dist/components/index.js +54 -0
  72. package/dist/components/listbox.d.ts +98 -0
  73. package/dist/components/listbox.d.ts.map +1 -0
  74. package/dist/components/listbox.js +174 -0
  75. package/dist/components/marquee.d.ts +84 -0
  76. package/dist/components/marquee.d.ts.map +1 -0
  77. package/dist/components/marquee.js +73 -0
  78. package/dist/components/menu.d.ts +131 -0
  79. package/dist/components/menu.d.ts.map +1 -0
  80. package/dist/components/menu.js +262 -0
  81. package/dist/components/navigation-menu.d.ts +111 -0
  82. package/dist/components/navigation-menu.d.ts.map +1 -0
  83. package/dist/components/navigation-menu.js +102 -0
  84. package/dist/components/number-input.d.ts +106 -0
  85. package/dist/components/number-input.d.ts.map +1 -0
  86. package/dist/components/number-input.js +178 -0
  87. package/dist/components/pagination.d.ts +113 -0
  88. package/dist/components/pagination.d.ts.map +1 -0
  89. package/dist/components/pagination.js +135 -0
  90. package/dist/components/password-input.d.ts +64 -0
  91. package/dist/components/password-input.d.ts.map +1 -0
  92. package/dist/components/password-input.js +52 -0
  93. package/dist/components/pin-input.d.ts +89 -0
  94. package/dist/components/pin-input.d.ts.map +1 -0
  95. package/dist/components/pin-input.js +139 -0
  96. package/dist/components/popover.d.ts +116 -0
  97. package/dist/components/popover.d.ts.map +1 -0
  98. package/dist/components/popover.js +146 -0
  99. package/dist/components/presence.d.ts +71 -0
  100. package/dist/components/presence.d.ts.map +1 -0
  101. package/dist/components/presence.js +57 -0
  102. package/dist/components/progress.d.ts +74 -0
  103. package/dist/components/progress.d.ts.map +1 -0
  104. package/dist/components/progress.js +80 -0
  105. package/dist/components/qr-code.d.ts +114 -0
  106. package/dist/components/qr-code.d.ts.map +1 -0
  107. package/dist/components/qr-code.js +108 -0
  108. package/dist/components/radio-group.d.ts +89 -0
  109. package/dist/components/radio-group.d.ts.map +1 -0
  110. package/dist/components/radio-group.js +161 -0
  111. package/dist/components/rating-group.d.ts +88 -0
  112. package/dist/components/rating-group.d.ts.map +1 -0
  113. package/dist/components/rating-group.js +122 -0
  114. package/dist/components/scroll-area.d.ts +124 -0
  115. package/dist/components/scroll-area.d.ts.map +1 -0
  116. package/dist/components/scroll-area.js +152 -0
  117. package/dist/components/select.d.ts +161 -0
  118. package/dist/components/select.d.ts.map +1 -0
  119. package/dist/components/select.js +333 -0
  120. package/dist/components/signature-pad.d.ts +138 -0
  121. package/dist/components/signature-pad.d.ts.map +1 -0
  122. package/dist/components/signature-pad.js +142 -0
  123. package/dist/components/slider.d.ts +117 -0
  124. package/dist/components/slider.d.ts.map +1 -0
  125. package/dist/components/slider.js +210 -0
  126. package/dist/components/splitter.d.ts +87 -0
  127. package/dist/components/splitter.d.ts.map +1 -0
  128. package/dist/components/splitter.js +119 -0
  129. package/dist/components/steps.d.ts +104 -0
  130. package/dist/components/steps.d.ts.map +1 -0
  131. package/dist/components/steps.js +133 -0
  132. package/dist/components/switch.d.ts +66 -0
  133. package/dist/components/switch.d.ts.map +1 -0
  134. package/dist/components/switch.js +59 -0
  135. package/dist/components/tabs.d.ts +146 -0
  136. package/dist/components/tabs.d.ts.map +1 -0
  137. package/dist/components/tabs.js +244 -0
  138. package/dist/components/tags-input.d.ts +118 -0
  139. package/dist/components/tags-input.d.ts.map +1 -0
  140. package/dist/components/tags-input.js +168 -0
  141. package/dist/components/time-picker.d.ts +121 -0
  142. package/dist/components/time-picker.d.ts.map +1 -0
  143. package/dist/components/time-picker.js +147 -0
  144. package/dist/components/timer.d.ts +131 -0
  145. package/dist/components/timer.d.ts.map +1 -0
  146. package/dist/components/timer.js +117 -0
  147. package/dist/components/toast.d.ts +119 -0
  148. package/dist/components/toast.d.ts.map +1 -0
  149. package/dist/components/toast.js +102 -0
  150. package/dist/components/toc.d.ts +119 -0
  151. package/dist/components/toc.d.ts.map +1 -0
  152. package/dist/components/toc.js +107 -0
  153. package/dist/components/toggle-group.d.ts +80 -0
  154. package/dist/components/toggle-group.d.ts.map +1 -0
  155. package/dist/components/toggle-group.js +93 -0
  156. package/dist/components/toggle.d.ts +47 -0
  157. package/dist/components/toggle.d.ts.map +1 -0
  158. package/dist/components/toggle.js +41 -0
  159. package/dist/components/tooltip.d.ts +92 -0
  160. package/dist/components/tooltip.d.ts.map +1 -0
  161. package/dist/components/tooltip.js +147 -0
  162. package/dist/components/tour.d.ts +145 -0
  163. package/dist/components/tour.d.ts.map +1 -0
  164. package/dist/components/tour.js +133 -0
  165. package/dist/components/tree-view.d.ts +216 -0
  166. package/dist/components/tree-view.d.ts.map +1 -0
  167. package/dist/components/tree-view.js +293 -0
  168. package/dist/index.d.ts +3 -0
  169. package/dist/index.d.ts.map +1 -0
  170. package/dist/index.js +4 -0
  171. package/dist/patterns/confirm-dialog.d.ts +92 -0
  172. package/dist/patterns/confirm-dialog.d.ts.map +1 -0
  173. package/dist/patterns/confirm-dialog.js +92 -0
  174. package/dist/patterns/index.d.ts +3 -0
  175. package/dist/patterns/index.d.ts.map +1 -0
  176. package/dist/patterns/index.js +1 -0
  177. package/dist/utils/anatomy.d.ts +40 -0
  178. package/dist/utils/anatomy.d.ts.map +1 -0
  179. package/dist/utils/anatomy.js +41 -0
  180. package/dist/utils/aria-hidden.d.ts +12 -0
  181. package/dist/utils/aria-hidden.d.ts.map +1 -0
  182. package/dist/utils/aria-hidden.js +72 -0
  183. package/dist/utils/dismissable.d.ts +25 -0
  184. package/dist/utils/dismissable.d.ts.map +1 -0
  185. package/dist/utils/dismissable.js +65 -0
  186. package/dist/utils/dom.d.ts +8 -0
  187. package/dist/utils/dom.d.ts.map +1 -0
  188. package/dist/utils/dom.js +21 -0
  189. package/dist/utils/floating.d.ts +44 -0
  190. package/dist/utils/floating.d.ts.map +1 -0
  191. package/dist/utils/floating.js +44 -0
  192. package/dist/utils/focus-trap.d.ts +18 -0
  193. package/dist/utils/focus-trap.d.ts.map +1 -0
  194. package/dist/utils/focus-trap.js +85 -0
  195. package/dist/utils/focusables.d.ts +6 -0
  196. package/dist/utils/focusables.d.ts.map +1 -0
  197. package/dist/utils/focusables.js +65 -0
  198. package/dist/utils/index.d.ts +18 -0
  199. package/dist/utils/index.d.ts.map +1 -0
  200. package/dist/utils/index.js +10 -0
  201. package/dist/utils/interact-outside.d.ts +26 -0
  202. package/dist/utils/interact-outside.d.ts.map +1 -0
  203. package/dist/utils/interact-outside.js +46 -0
  204. package/dist/utils/remove-scroll.d.ts +8 -0
  205. package/dist/utils/remove-scroll.d.ts.map +1 -0
  206. package/dist/utils/remove-scroll.js +37 -0
  207. package/dist/utils/tree-collection.d.ts +61 -0
  208. package/dist/utils/tree-collection.d.ts.map +1 -0
  209. package/dist/utils/tree-collection.js +137 -0
  210. package/dist/utils/typeahead.d.ts +49 -0
  211. package/dist/utils/typeahead.d.ts.map +1 -0
  212. package/dist/utils/typeahead.js +81 -0
  213. package/package.json +282 -0
@@ -0,0 +1,293 @@
1
+ import { typeaheadAccumulate, typeaheadMatch, isTypeaheadKey, TYPEAHEAD_TIMEOUT_MS, } from '../utils/typeahead';
2
+ export function init(opts = {}) {
3
+ return {
4
+ expanded: opts.expanded ?? [],
5
+ selected: opts.selected ?? [],
6
+ checked: opts.checked ?? [],
7
+ indeterminate: opts.indeterminate ?? [],
8
+ focused: null,
9
+ selectionMode: opts.selectionMode ?? 'single',
10
+ visibleItems: opts.visibleItems ?? [],
11
+ visibleLabels: opts.visibleLabels ?? [],
12
+ disabled: opts.disabled ?? false,
13
+ typeahead: '',
14
+ typeaheadExpiresAt: 0,
15
+ renaming: null,
16
+ renameDraft: '',
17
+ loading: [],
18
+ };
19
+ }
20
+ export function update(state, msg) {
21
+ if (state.disabled && msg.type !== 'setVisibleItems')
22
+ return [state, []];
23
+ switch (msg.type) {
24
+ case 'toggleBranch': {
25
+ const expanded = state.expanded.includes(msg.id)
26
+ ? state.expanded.filter((id) => id !== msg.id)
27
+ : [...state.expanded, msg.id];
28
+ return [{ ...state, expanded }, []];
29
+ }
30
+ case 'expand':
31
+ if (state.expanded.includes(msg.id))
32
+ return [state, []];
33
+ return [{ ...state, expanded: [...state.expanded, msg.id] }, []];
34
+ case 'collapse':
35
+ if (!state.expanded.includes(msg.id))
36
+ return [state, []];
37
+ return [{ ...state, expanded: state.expanded.filter((id) => id !== msg.id) }, []];
38
+ case 'expandAll':
39
+ return [{ ...state, expanded: msg.ids }, []];
40
+ case 'collapseAll':
41
+ return [{ ...state, expanded: [] }, []];
42
+ case 'select': {
43
+ if (state.selectionMode === 'single') {
44
+ return [{ ...state, selected: [msg.id] }, []];
45
+ }
46
+ if (msg.additive) {
47
+ const isSelected = state.selected.includes(msg.id);
48
+ const selected = isSelected
49
+ ? state.selected.filter((id) => id !== msg.id)
50
+ : [...state.selected, msg.id];
51
+ return [{ ...state, selected }, []];
52
+ }
53
+ return [{ ...state, selected: [msg.id] }, []];
54
+ }
55
+ case 'setSelected':
56
+ return [{ ...state, selected: msg.ids }, []];
57
+ case 'focus':
58
+ return [{ ...state, focused: msg.id }, []];
59
+ case 'focusNext': {
60
+ if (state.visibleItems.length === 0)
61
+ return [state, []];
62
+ const idx = state.focused ? state.visibleItems.indexOf(state.focused) : -1;
63
+ const next = state.visibleItems[Math.min(idx + 1, state.visibleItems.length - 1)];
64
+ return [{ ...state, focused: next ?? state.focused }, []];
65
+ }
66
+ case 'focusPrev': {
67
+ if (state.visibleItems.length === 0)
68
+ return [state, []];
69
+ const idx = state.focused
70
+ ? state.visibleItems.indexOf(state.focused)
71
+ : state.visibleItems.length;
72
+ const prev = state.visibleItems[Math.max(0, idx - 1)];
73
+ return [{ ...state, focused: prev ?? state.focused }, []];
74
+ }
75
+ case 'focusFirst':
76
+ return [{ ...state, focused: state.visibleItems[0] ?? null }, []];
77
+ case 'focusLast':
78
+ return [{ ...state, focused: state.visibleItems[state.visibleItems.length - 1] ?? null }, []];
79
+ case 'setVisibleItems':
80
+ return [
81
+ { ...state, visibleItems: msg.ids, visibleLabels: msg.labels ?? state.visibleLabels },
82
+ [],
83
+ ];
84
+ case 'arrowLeftFrom': {
85
+ // If this is an expanded branch, collapse it and stay focused.
86
+ if (msg.isBranch && state.expanded.includes(msg.id)) {
87
+ return [{ ...state, expanded: state.expanded.filter((id) => id !== msg.id) }, []];
88
+ }
89
+ // Otherwise, move focus to parent if provided (WAI-ARIA).
90
+ if (msg.parentId !== null) {
91
+ return [{ ...state, focused: msg.parentId }, []];
92
+ }
93
+ return [state, []];
94
+ }
95
+ case 'arrowRightFrom': {
96
+ // If this branch is closed, expand it; otherwise focus the first
97
+ // visible child (the next item in depth-first visibleItems order).
98
+ if (!state.expanded.includes(msg.id)) {
99
+ return [{ ...state, expanded: [...state.expanded, msg.id] }, []];
100
+ }
101
+ const idx = state.visibleItems.indexOf(msg.id);
102
+ if (idx === -1 || idx === state.visibleItems.length - 1)
103
+ return [state, []];
104
+ const next = state.visibleItems[idx + 1];
105
+ return [{ ...state, focused: next }, []];
106
+ }
107
+ case 'typeahead': {
108
+ if (state.visibleItems.length === 0)
109
+ return [state, []];
110
+ const acc = typeaheadAccumulate(state.typeahead, msg.char, msg.now, state.typeaheadExpiresAt);
111
+ // Fall back to matching ids if labels weren't provided.
112
+ const labels = state.visibleLabels.length > 0 ? state.visibleLabels : state.visibleItems;
113
+ const disabledMask = new Array(labels.length).fill(false);
114
+ const startIdx = state.focused ? state.visibleItems.indexOf(state.focused) : null;
115
+ const matchIdx = typeaheadMatch(labels, disabledMask, acc, startIdx);
116
+ const focused = matchIdx === null ? state.focused : (state.visibleItems[matchIdx] ?? state.focused);
117
+ return [
118
+ { ...state, typeahead: acc, typeaheadExpiresAt: msg.now + TYPEAHEAD_TIMEOUT_MS, focused },
119
+ [],
120
+ ];
121
+ }
122
+ case 'toggleChecked': {
123
+ // Toggle the item's checked state, propagating to descendants if any.
124
+ // The caller passes `descendantIds` for branches; for leaves, pass
125
+ // an empty list or omit. Indeterminate flag is cleared on the id
126
+ // (a deliberate toggle is a definite state). The caller is
127
+ // responsible for recomputing `indeterminate` on ancestors via
128
+ // setIndeterminate after this message.
129
+ const desc = msg.descendantIds ?? [];
130
+ const all = [msg.id, ...desc];
131
+ const isChecked = state.checked.includes(msg.id);
132
+ const next = isChecked
133
+ ? state.checked.filter((id) => !all.includes(id))
134
+ : Array.from(new Set([...state.checked, ...all]));
135
+ const indeterminate = state.indeterminate.filter((id) => !all.includes(id));
136
+ return [{ ...state, checked: next, indeterminate }, []];
137
+ }
138
+ case 'setChecked':
139
+ return [{ ...state, checked: msg.ids }, []];
140
+ case 'setIndeterminate':
141
+ return [{ ...state, indeterminate: msg.ids }, []];
142
+ case 'renameStart':
143
+ return [{ ...state, renaming: msg.id, renameDraft: msg.initial }, []];
144
+ case 'renameChange':
145
+ return [{ ...state, renameDraft: msg.value }, []];
146
+ case 'renameCommit':
147
+ case 'renameCancel':
148
+ return [{ ...state, renaming: null, renameDraft: '' }, []];
149
+ case 'loadingStart':
150
+ if (state.loading.includes(msg.id))
151
+ return [state, []];
152
+ return [{ ...state, loading: [...state.loading, msg.id] }, []];
153
+ case 'loadingEnd':
154
+ return [{ ...state, loading: state.loading.filter((id) => id !== msg.id) }, []];
155
+ }
156
+ }
157
+ export function isExpanded(state, id) {
158
+ return state.expanded.includes(id);
159
+ }
160
+ export function isSelected(state, id) {
161
+ return state.selected.includes(id);
162
+ }
163
+ export function isChecked(state, id) {
164
+ return state.checked.includes(id);
165
+ }
166
+ export function isIndeterminate(state, id) {
167
+ return state.indeterminate.includes(id);
168
+ }
169
+ export function isRenaming(state, id) {
170
+ return state.renaming === id;
171
+ }
172
+ export function isLoading(state, id) {
173
+ return state.loading.includes(id);
174
+ }
175
+ export function connect(get, send, opts) {
176
+ const itemId = (v) => `${opts.id}:item:${v}`;
177
+ const expandOnClick = opts.expandOnClick === true;
178
+ return {
179
+ root: {
180
+ role: 'tree',
181
+ 'aria-multiselectable': (s) => (get(s).selectionMode === 'multiple' ? 'true' : undefined),
182
+ 'aria-disabled': (s) => (get(s).disabled ? 'true' : undefined),
183
+ 'data-scope': 'tree-view',
184
+ 'data-part': 'root',
185
+ 'data-disabled': (s) => (get(s).disabled ? '' : undefined),
186
+ },
187
+ item: (id, depth, isBranch, parentId = null) => ({
188
+ item: {
189
+ role: 'treeitem',
190
+ id: itemId(id),
191
+ 'aria-expanded': (s) => (isBranch ? isExpanded(get(s), id) : undefined),
192
+ 'aria-selected': (s) => get(s).selectionMode === 'single' ? isSelected(get(s), id) : undefined,
193
+ 'aria-level': depth + 1,
194
+ 'aria-busy': (s) => (isLoading(get(s), id) ? 'true' : undefined),
195
+ tabIndex: (s) => (get(s).focused === id ? 0 : -1),
196
+ 'data-scope': 'tree-view',
197
+ 'data-part': 'item',
198
+ 'data-value': id,
199
+ 'data-depth': String(depth),
200
+ 'data-selected': (s) => (isSelected(get(s), id) ? '' : undefined),
201
+ 'data-focused': (s) => (get(s).focused === id ? '' : undefined),
202
+ 'data-loading': (s) => (isLoading(get(s), id) ? '' : undefined),
203
+ onClick: (e) => {
204
+ send({ type: 'select', id, additive: e.metaKey || e.ctrlKey });
205
+ if (expandOnClick && isBranch)
206
+ send({ type: 'toggleBranch', id });
207
+ },
208
+ onFocus: () => send({ type: 'focus', id }),
209
+ onKeyDown: (e) => {
210
+ switch (e.key) {
211
+ case 'ArrowDown':
212
+ e.preventDefault();
213
+ send({ type: 'focusNext' });
214
+ return;
215
+ case 'ArrowUp':
216
+ e.preventDefault();
217
+ send({ type: 'focusPrev' });
218
+ return;
219
+ case 'ArrowRight':
220
+ // WAI-ARIA: closed branch → expand (stay); open branch →
221
+ // focus first child. Leaf → nothing. The reducer decides
222
+ // based on current expanded state.
223
+ if (!isBranch)
224
+ return;
225
+ e.preventDefault();
226
+ send({ type: 'arrowRightFrom', id });
227
+ return;
228
+ case 'ArrowLeft':
229
+ // WAI-ARIA: open branch → collapse (stay); closed branch or
230
+ // leaf → focus parent (if known). Root end-nodes → nothing.
231
+ e.preventDefault();
232
+ send({ type: 'arrowLeftFrom', id, isBranch, parentId });
233
+ return;
234
+ case 'Home':
235
+ e.preventDefault();
236
+ send({ type: 'focusFirst' });
237
+ return;
238
+ case 'End':
239
+ e.preventDefault();
240
+ send({ type: 'focusLast' });
241
+ return;
242
+ case 'Enter':
243
+ case ' ':
244
+ e.preventDefault();
245
+ send({ type: 'select', id, additive: e.metaKey || e.ctrlKey });
246
+ if (isBranch)
247
+ send({ type: 'toggleBranch', id });
248
+ return;
249
+ default:
250
+ if (isTypeaheadKey(e)) {
251
+ send({ type: 'typeahead', char: e.key, now: Date.now() });
252
+ }
253
+ }
254
+ },
255
+ },
256
+ branchTrigger: {
257
+ 'data-scope': 'tree-view',
258
+ 'data-part': 'branch-trigger',
259
+ 'data-state': (s) => (isExpanded(get(s), id) ? 'open' : 'closed'),
260
+ onClick: (e) => {
261
+ e.stopPropagation();
262
+ send({ type: 'toggleBranch', id });
263
+ },
264
+ },
265
+ checkbox: {
266
+ role: 'checkbox',
267
+ 'aria-checked': (s) => {
268
+ if (isIndeterminate(get(s), id))
269
+ return 'mixed';
270
+ return isChecked(get(s), id) ? 'true' : 'false';
271
+ },
272
+ 'data-scope': 'tree-view',
273
+ 'data-part': 'checkbox',
274
+ 'data-state': (s) => {
275
+ if (isIndeterminate(get(s), id))
276
+ return 'indeterminate';
277
+ return isChecked(get(s), id) ? 'checked' : 'unchecked';
278
+ },
279
+ },
280
+ }),
281
+ };
282
+ }
283
+ export const treeView = {
284
+ init,
285
+ update,
286
+ connect,
287
+ isExpanded,
288
+ isSelected,
289
+ isChecked,
290
+ isIndeterminate,
291
+ isRenaming,
292
+ isLoading,
293
+ };
@@ -0,0 +1,3 @@
1
+ export * from './utils/index';
2
+ export * from './components/index';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,eAAe,CAAA;AAE7B,cAAc,oBAAoB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // Utilities — re-exported from @llui/components/utils for convenience.
2
+ export * from './utils/index';
3
+ // Components
4
+ export * from './components/index';
@@ -0,0 +1,92 @@
1
+ import type { Send, TransitionOptions } from '@llui/dom';
2
+ import { init as dialogInit, update as dialogUpdate } from '../components/dialog';
3
+ /**
4
+ * ConfirmDialog — a pre-wired dialog pattern for confirmations.
5
+ *
6
+ * Composes `dialog` with conventional content: title, description, cancel,
7
+ * confirm. Carries an opaque `tag` so the consumer's update handler can
8
+ * recognize which confirmation resolved.
9
+ *
10
+ * Usage in consumer's update:
11
+ *
12
+ * ```ts
13
+ * case 'confirm': {
14
+ * const [s, fx] = confirmDialog.update(state.confirm, msg.msg)
15
+ * // When the user clicks confirm, branch on the tag:
16
+ * if (msg.msg.type === 'confirm') {
17
+ * switch (state.confirm.tag) {
18
+ * case 'delete-user': return [{ ...state, confirm: s, users: ... }, fx]
19
+ * case 'logout': return [{ ...state, confirm: s }, [...fx, logoutEffect]]
20
+ * }
21
+ * }
22
+ * return [{ ...state, confirm: s }, fx]
23
+ * }
24
+ * ```
25
+ */
26
+ export interface ConfirmDialogState {
27
+ open: boolean;
28
+ tag: string;
29
+ title: string;
30
+ description: string;
31
+ confirmLabel: string;
32
+ cancelLabel: string;
33
+ destructive: boolean;
34
+ }
35
+ export type ConfirmDialogMsg = {
36
+ type: 'openWith';
37
+ tag: string;
38
+ title: string;
39
+ description?: string;
40
+ confirmLabel?: string;
41
+ cancelLabel?: string;
42
+ destructive?: boolean;
43
+ } | {
44
+ type: 'confirm';
45
+ } | {
46
+ type: 'cancel';
47
+ } | {
48
+ type: 'setOpen';
49
+ open: boolean;
50
+ };
51
+ export interface ConfirmDialogInit {
52
+ tag?: string;
53
+ title?: string;
54
+ description?: string;
55
+ confirmLabel?: string;
56
+ cancelLabel?: string;
57
+ destructive?: boolean;
58
+ }
59
+ export declare function init(opts?: ConfirmDialogInit): ConfirmDialogState;
60
+ export declare function update(state: ConfirmDialogState, msg: ConfirmDialogMsg): [ConfirmDialogState, never[]];
61
+ /**
62
+ * View the ConfirmDialog. Returns a `show`-wrapped tree that appears when
63
+ * the dialog is open, renders default content (title/description/buttons).
64
+ * Uses role="alertdialog" for destructive confirms.
65
+ */
66
+ export interface ConfirmDialogViewOptions<S> {
67
+ get: (s: S) => ConfirmDialogState;
68
+ send: Send<ConfirmDialogMsg>;
69
+ id: string;
70
+ transition?: TransitionOptions;
71
+ /** Custom class for content root. */
72
+ contentClass?: string;
73
+ /** Custom class for destructive confirm button. */
74
+ destructiveClass?: string;
75
+ }
76
+ export declare function view<S>(opts: ConfirmDialogViewOptions<S>): Node[];
77
+ /** Helper to create an openWith message builder. */
78
+ export declare function openWith(tag: string, opts: {
79
+ title: string;
80
+ description?: string;
81
+ confirmLabel?: string;
82
+ cancelLabel?: string;
83
+ destructive?: boolean;
84
+ }): ConfirmDialogMsg;
85
+ export declare const confirmDialog: {
86
+ init: typeof init;
87
+ update: typeof update;
88
+ view: typeof view;
89
+ openWith: typeof openWith;
90
+ };
91
+ export { dialogInit, dialogUpdate };
92
+ //# sourceMappingURL=confirm-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm-dialog.d.ts","sourceRoot":"","sources":["../../src/patterns/confirm-dialog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAExD,OAAO,EACL,IAAI,IAAI,UAAU,EAClB,MAAM,IAAI,YAAY,EAIvB,MAAM,sBAAsB,CAAA;AAE7B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,MAAM,gBAAgB,GACxB;IACE,IAAI,EAAE,UAAU,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,GACD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAEtC,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,wBAAgB,IAAI,CAAC,IAAI,GAAE,iBAAsB,GAAG,kBAAkB,CAUrE;AAED,wBAAgB,MAAM,CACpB,KAAK,EAAE,kBAAkB,EACzB,GAAG,EAAE,gBAAgB,GACpB,CAAC,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAuB/B;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACzC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,kBAAkB,CAAA;IACjC,IAAI,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAkDjE;AAED,oDAAoD;AACpD,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IACJ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,GACA,gBAAgB,CAUlB;AAED,eAAO,MAAM,aAAa;;;;;CAAmC,CAAA;AAG7D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,CAAA"}
@@ -0,0 +1,92 @@
1
+ import { button, text, div, h2, p } from '@llui/dom';
2
+ import { init as dialogInit, update as dialogUpdate, connect as dialogConnect, overlay as dialogOverlay, } from '../components/dialog';
3
+ export function init(opts = {}) {
4
+ return {
5
+ open: false,
6
+ tag: opts.tag ?? '',
7
+ title: opts.title ?? '',
8
+ description: opts.description ?? '',
9
+ confirmLabel: opts.confirmLabel ?? 'Confirm',
10
+ cancelLabel: opts.cancelLabel ?? 'Cancel',
11
+ destructive: opts.destructive ?? false,
12
+ };
13
+ }
14
+ export function update(state, msg) {
15
+ switch (msg.type) {
16
+ case 'openWith':
17
+ return [
18
+ {
19
+ ...state,
20
+ open: true,
21
+ tag: msg.tag,
22
+ title: msg.title,
23
+ description: msg.description ?? '',
24
+ confirmLabel: msg.confirmLabel ?? state.confirmLabel,
25
+ cancelLabel: msg.cancelLabel ?? state.cancelLabel,
26
+ destructive: msg.destructive ?? false,
27
+ },
28
+ [],
29
+ ];
30
+ case 'confirm':
31
+ return [{ ...state, open: false }, []];
32
+ case 'cancel':
33
+ return [{ ...state, open: false }, []];
34
+ case 'setOpen':
35
+ return [{ ...state, open: msg.open }, []];
36
+ }
37
+ }
38
+ export function view(opts) {
39
+ // Build dialog parts — role='alertdialog' for proper modal semantics.
40
+ // Trigger + closeTrigger parts are unused (we provide our own buttons);
41
+ // their send is a no-op.
42
+ const parts = dialogConnect((s) => ({ open: opts.get(s).open }), () => {
43
+ /* unused — our buttons dispatch directly via opts.send */
44
+ }, { id: opts.id, role: 'alertdialog', closeLabel: 'Cancel' });
45
+ return dialogOverlay({
46
+ get: (s) => ({ open: opts.get(s).open }),
47
+ // Dismissable (Esc/outside-click) dispatches dialog.close, which we
48
+ // translate into confirm-dialog.cancel.
49
+ send: (m) => {
50
+ if (m.type === 'close')
51
+ opts.send({ type: 'cancel' });
52
+ },
53
+ parts,
54
+ content: () => [
55
+ div({ ...parts.content, class: opts.contentClass ?? 'confirm-dialog' }, [
56
+ h2({ ...parts.title }, [text((s) => opts.get(s).title)]),
57
+ p({ ...parts.description }, [text((s) => opts.get(s).description)]),
58
+ div({ class: 'confirm-dialog__actions' }, [
59
+ button({
60
+ type: 'button',
61
+ class: 'btn btn-secondary',
62
+ onClick: () => opts.send({ type: 'cancel' }),
63
+ }, [text((s) => opts.get(s).cancelLabel)]),
64
+ button({
65
+ type: 'button',
66
+ class: (s) => opts.get(s).destructive
67
+ ? (opts.destructiveClass ?? 'btn btn-danger')
68
+ : 'btn btn-primary',
69
+ onClick: () => opts.send({ type: 'confirm' }),
70
+ }, [text((s) => opts.get(s).confirmLabel)]),
71
+ ]),
72
+ ]),
73
+ ],
74
+ transition: opts.transition,
75
+ closeOnOutsideClick: false, // alertdialog default
76
+ });
77
+ }
78
+ /** Helper to create an openWith message builder. */
79
+ export function openWith(tag, opts) {
80
+ return {
81
+ type: 'openWith',
82
+ tag,
83
+ title: opts.title,
84
+ description: opts.description,
85
+ confirmLabel: opts.confirmLabel,
86
+ cancelLabel: opts.cancelLabel,
87
+ destructive: opts.destructive,
88
+ };
89
+ }
90
+ export const confirmDialog = { init, update, view, openWith };
91
+ // Re-exported dialog primitives (available for advanced use cases)
92
+ export { dialogInit, dialogUpdate };
@@ -0,0 +1,3 @@
1
+ export * as confirmDialog from './confirm-dialog';
2
+ export type { ConfirmDialogState, ConfirmDialogMsg, ConfirmDialogInit, ConfirmDialogViewOptions, } from './confirm-dialog';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patterns/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1 @@
1
+ export * as confirmDialog from './confirm-dialog';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Anatomy — describes a component's parts and assigns unique ids per instance.
3
+ *
4
+ * Each component (dialog, menu, tabs, ...) declares its parts once. Every
5
+ * mount creates a fresh scope with unique ids used for ARIA wiring
6
+ * (`aria-controls`, `aria-labelledby`) and `data-scope`/`data-part` for CSS.
7
+ *
8
+ * ```ts
9
+ * const DialogAnatomy = anatomy('dialog', [
10
+ * 'trigger', 'positioner', 'backdrop', 'content', 'title', 'description', 'closeTrigger',
11
+ * ] as const)
12
+ *
13
+ * // Per instance:
14
+ * const parts = DialogAnatomy.scope()
15
+ * parts.attrs('trigger') // { id: 'dialog-1:trigger', 'data-scope': 'dialog', 'data-part': 'trigger' }
16
+ * parts.idFor('content') // 'dialog-1:content'
17
+ * ```
18
+ */
19
+ /** Reset the internal id counter — tests only. */
20
+ export declare function resetAnatomyIdCounter(): void;
21
+ export interface AnatomyScope<P extends string> {
22
+ /** Instance id — unique across all anatomy scopes. */
23
+ readonly id: string;
24
+ /** Resolve the id for a specific part (for ARIA wiring). */
25
+ idFor(part: P): string;
26
+ /** Build the common data-attrs + id for a part. */
27
+ attrs(part: P): {
28
+ id: string;
29
+ 'data-scope': string;
30
+ 'data-part': P;
31
+ };
32
+ }
33
+ export interface Anatomy<P extends string> {
34
+ readonly name: string;
35
+ readonly parts: readonly P[];
36
+ /** Create a new scope instance. Pass an explicit id to force a value (SSR). */
37
+ scope(id?: string): AnatomyScope<P>;
38
+ }
39
+ export declare function anatomy<P extends string>(name: string, parts: readonly P[]): Anatomy<P>;
40
+ //# sourceMappingURL=anatomy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anatomy.d.ts","sourceRoot":"","sources":["../../src/utils/anatomy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,kDAAkD;AAClD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM;IAC5C,sDAAsD;IACtD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,4DAA4D;IAC5D,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAA;IACtB,mDAAmD;IACnD,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,CAAC,CAAA;KAAE,CAAA;CACrE;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAA;IAC5B,+EAA+E;IAC/E,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAA;CACpC;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAiBvF"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Anatomy — describes a component's parts and assigns unique ids per instance.
3
+ *
4
+ * Each component (dialog, menu, tabs, ...) declares its parts once. Every
5
+ * mount creates a fresh scope with unique ids used for ARIA wiring
6
+ * (`aria-controls`, `aria-labelledby`) and `data-scope`/`data-part` for CSS.
7
+ *
8
+ * ```ts
9
+ * const DialogAnatomy = anatomy('dialog', [
10
+ * 'trigger', 'positioner', 'backdrop', 'content', 'title', 'description', 'closeTrigger',
11
+ * ] as const)
12
+ *
13
+ * // Per instance:
14
+ * const parts = DialogAnatomy.scope()
15
+ * parts.attrs('trigger') // { id: 'dialog-1:trigger', 'data-scope': 'dialog', 'data-part': 'trigger' }
16
+ * parts.idFor('content') // 'dialog-1:content'
17
+ * ```
18
+ */
19
+ let counter = 0;
20
+ /** Reset the internal id counter — tests only. */
21
+ export function resetAnatomyIdCounter() {
22
+ counter = 0;
23
+ }
24
+ export function anatomy(name, parts) {
25
+ return {
26
+ name,
27
+ parts,
28
+ scope(id) {
29
+ const scopeId = id ?? `${name}-${++counter}`;
30
+ return {
31
+ id: scopeId,
32
+ idFor: (part) => `${scopeId}:${part}`,
33
+ attrs: (part) => ({
34
+ id: `${scopeId}:${part}`,
35
+ 'data-scope': name,
36
+ 'data-part': part,
37
+ }),
38
+ };
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Hide sibling subtrees from assistive tech while an overlay is open.
3
+ *
4
+ * Walks from `target` up to the document root, applying `aria-hidden="true"`
5
+ * and `inert` to every sibling at each level. Previous attribute values are
6
+ * recorded and restored on cleanup.
7
+ *
8
+ * Nested calls are supported — each layer only touches elements that haven't
9
+ * been claimed by a higher layer (tracked via a WeakMap reference count).
10
+ */
11
+ export declare function setAriaHiddenOutside(target: Element): () => void;
12
+ //# sourceMappingURL=aria-hidden.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aria-hidden.d.ts","sourceRoot":"","sources":["../../src/utils/aria-hidden.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,CAoChE"}