@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,262 @@
1
+ import { show, portal, onMount, div } from '@llui/dom';
2
+ import { pushDismissable } from '../utils/dismissable';
3
+ import { attachFloating } from '../utils/floating';
4
+ import { typeaheadAccumulate, typeaheadMatchByItems, isTypeaheadKey, TYPEAHEAD_TIMEOUT_MS, } from '../utils/typeahead';
5
+ export function init(opts = {}) {
6
+ return {
7
+ open: opts.open ?? false,
8
+ items: opts.items ?? [],
9
+ disabledItems: opts.disabledItems ?? [],
10
+ highlighted: opts.highlighted ?? null,
11
+ typeahead: '',
12
+ typeaheadExpiresAt: 0,
13
+ };
14
+ }
15
+ function firstEnabled(items, disabled) {
16
+ for (const v of items)
17
+ if (!disabled.includes(v))
18
+ return v;
19
+ return null;
20
+ }
21
+ function lastEnabled(items, disabled) {
22
+ for (let i = items.length - 1; i >= 0; i--) {
23
+ const v = items[i];
24
+ if (!disabled.includes(v))
25
+ return v;
26
+ }
27
+ return null;
28
+ }
29
+ function nextEnabled(items, disabled, from, delta) {
30
+ if (items.length === 0)
31
+ return null;
32
+ const start = from === null ? -1 : items.indexOf(from);
33
+ const n = items.length;
34
+ for (let i = 1; i <= n; i++) {
35
+ const idx = start === -1 && delta === 1 ? i - 1 : (start + delta * i + n * n) % n;
36
+ const v = items[idx];
37
+ if (!disabled.includes(v))
38
+ return v;
39
+ }
40
+ return null;
41
+ }
42
+ export function update(state, msg) {
43
+ switch (msg.type) {
44
+ case 'open': {
45
+ const highlighted = state.highlighted ?? firstEnabled(state.items, state.disabledItems);
46
+ return [{ ...state, open: true, highlighted }, []];
47
+ }
48
+ case 'close':
49
+ return [{ ...state, open: false, highlighted: null, typeahead: '' }, []];
50
+ case 'toggle':
51
+ if (state.open) {
52
+ return [{ ...state, open: false, highlighted: null, typeahead: '' }, []];
53
+ }
54
+ return [
55
+ {
56
+ ...state,
57
+ open: true,
58
+ highlighted: state.highlighted ?? firstEnabled(state.items, state.disabledItems),
59
+ },
60
+ [],
61
+ ];
62
+ case 'highlight':
63
+ if (msg.value !== null && state.disabledItems.includes(msg.value))
64
+ return [state, []];
65
+ return [{ ...state, highlighted: msg.value }, []];
66
+ case 'highlightNext': {
67
+ const to = nextEnabled(state.items, state.disabledItems, state.highlighted, 1);
68
+ return [{ ...state, highlighted: to }, []];
69
+ }
70
+ case 'highlightPrev': {
71
+ const to = nextEnabled(state.items, state.disabledItems, state.highlighted, -1);
72
+ return [{ ...state, highlighted: to }, []];
73
+ }
74
+ case 'highlightFirst':
75
+ return [{ ...state, highlighted: firstEnabled(state.items, state.disabledItems) }, []];
76
+ case 'highlightLast':
77
+ return [{ ...state, highlighted: lastEnabled(state.items, state.disabledItems) }, []];
78
+ case 'selectHighlighted':
79
+ if (state.highlighted === null)
80
+ return [state, []];
81
+ return [{ ...state, open: false, highlighted: null, typeahead: '' }, []];
82
+ case 'select':
83
+ if (state.disabledItems.includes(msg.value))
84
+ return [state, []];
85
+ return [{ ...state, open: false, highlighted: null, typeahead: '' }, []];
86
+ case 'setItems': {
87
+ const disabled = msg.disabled ?? state.disabledItems;
88
+ const highlighted = state.highlighted &&
89
+ msg.items.includes(state.highlighted) &&
90
+ !disabled.includes(state.highlighted)
91
+ ? state.highlighted
92
+ : null;
93
+ return [{ ...state, items: msg.items, disabledItems: disabled, highlighted }, []];
94
+ }
95
+ case 'typeahead': {
96
+ const acc = typeaheadAccumulate(state.typeahead, msg.char, msg.now, state.typeaheadExpiresAt);
97
+ const startIdx = state.highlighted ? state.items.indexOf(state.highlighted) : null;
98
+ const matchIdx = typeaheadMatchByItems(state.items, state.disabledItems, acc, startIdx);
99
+ const match = matchIdx === null ? null : state.items[matchIdx];
100
+ return [
101
+ {
102
+ ...state,
103
+ typeahead: acc,
104
+ typeaheadExpiresAt: msg.now + TYPEAHEAD_TIMEOUT_MS,
105
+ highlighted: match ?? state.highlighted,
106
+ },
107
+ [],
108
+ ];
109
+ }
110
+ }
111
+ }
112
+ export function connect(get, send, opts) {
113
+ const base = opts.id;
114
+ const triggerId = `${base}:trigger`;
115
+ const contentId = `${base}:content`;
116
+ const itemId = (v) => `${base}:item:${v}`;
117
+ const handleMenuKey = (e) => {
118
+ switch (e.key) {
119
+ case 'ArrowDown':
120
+ e.preventDefault();
121
+ send({ type: 'highlightNext' });
122
+ return;
123
+ case 'ArrowUp':
124
+ e.preventDefault();
125
+ send({ type: 'highlightPrev' });
126
+ return;
127
+ case 'Home':
128
+ e.preventDefault();
129
+ send({ type: 'highlightFirst' });
130
+ return;
131
+ case 'End':
132
+ e.preventDefault();
133
+ send({ type: 'highlightLast' });
134
+ return;
135
+ case 'Enter':
136
+ case ' ':
137
+ e.preventDefault();
138
+ send({ type: 'selectHighlighted' });
139
+ return;
140
+ case 'Escape':
141
+ e.preventDefault();
142
+ send({ type: 'close' });
143
+ return;
144
+ default:
145
+ if (isTypeaheadKey(e)) {
146
+ send({ type: 'typeahead', char: e.key, now: Date.now() });
147
+ }
148
+ }
149
+ };
150
+ return {
151
+ trigger: {
152
+ type: 'button',
153
+ 'aria-haspopup': 'menu',
154
+ 'aria-expanded': (s) => get(s).open,
155
+ 'aria-controls': contentId,
156
+ id: triggerId,
157
+ 'data-state': (s) => (get(s).open ? 'open' : 'closed'),
158
+ 'data-scope': 'menu',
159
+ 'data-part': 'trigger',
160
+ onClick: () => send({ type: 'toggle' }),
161
+ onKeyDown: (e) => {
162
+ if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
163
+ e.preventDefault();
164
+ send({ type: 'open' });
165
+ }
166
+ else if (e.key === 'ArrowUp') {
167
+ e.preventDefault();
168
+ send({ type: 'open' });
169
+ send({ type: 'highlightLast' });
170
+ }
171
+ },
172
+ },
173
+ positioner: {
174
+ 'data-scope': 'menu',
175
+ 'data-part': 'positioner',
176
+ style: 'position:absolute;top:0;left:0;',
177
+ },
178
+ content: {
179
+ role: 'menu',
180
+ id: contentId,
181
+ 'aria-labelledby': triggerId,
182
+ tabIndex: -1,
183
+ 'data-state': (s) => (get(s).open ? 'open' : 'closed'),
184
+ 'data-scope': 'menu',
185
+ 'data-part': 'content',
186
+ onKeyDown: handleMenuKey,
187
+ },
188
+ item: (value) => ({
189
+ item: {
190
+ role: 'menuitem',
191
+ id: itemId(value),
192
+ 'aria-disabled': (s) => (get(s).disabledItems.includes(value) ? 'true' : undefined),
193
+ 'data-state': (s) => (get(s).highlighted === value ? 'highlighted' : undefined),
194
+ 'data-disabled': (s) => (get(s).disabledItems.includes(value) ? '' : undefined),
195
+ 'data-scope': 'menu',
196
+ 'data-part': 'item',
197
+ 'data-value': value,
198
+ tabIndex: -1,
199
+ onClick: () => {
200
+ send({ type: 'select', value });
201
+ opts.onSelect?.(value);
202
+ },
203
+ onPointerMove: () => send({ type: 'highlight', value }),
204
+ },
205
+ }),
206
+ };
207
+ }
208
+ export function overlay(opts) {
209
+ const target = opts.target ?? 'body';
210
+ const placement = opts.placement ?? 'bottom-start';
211
+ const offset = opts.offset ?? 4;
212
+ const flip = opts.flip !== false;
213
+ const shift = opts.shift !== false;
214
+ const parts = opts.parts;
215
+ const contentId = parts.content.id;
216
+ const triggerId = parts.trigger.id;
217
+ return show({
218
+ when: (s) => opts.get(s).open,
219
+ render: () => portal({
220
+ target,
221
+ render: () => {
222
+ onMount(() => {
223
+ const contentEl = document.getElementById(contentId);
224
+ const triggerEl = document.getElementById(triggerId);
225
+ if (!contentEl || !triggerEl)
226
+ return;
227
+ const cleanups = [];
228
+ const positioner = contentEl.closest('[data-part="positioner"]');
229
+ const floatingEl = positioner ?? contentEl;
230
+ cleanups.push(attachFloating({
231
+ anchor: triggerEl,
232
+ floating: floatingEl,
233
+ placement,
234
+ offset,
235
+ flip,
236
+ shift,
237
+ }));
238
+ cleanups.push(pushDismissable({
239
+ element: contentEl,
240
+ ignore: () => [triggerEl],
241
+ onDismiss: () => {
242
+ opts.send({ type: 'close' });
243
+ triggerEl.focus();
244
+ },
245
+ }));
246
+ // Auto-focus content so keyboard navigation works immediately.
247
+ // preventScroll avoids a page jump when the portaled content
248
+ // is briefly at position (0,0) before floating-ui positions it.
249
+ contentEl.focus({ preventScroll: true });
250
+ return () => {
251
+ for (let i = cleanups.length - 1; i >= 0; i--)
252
+ cleanups[i]();
253
+ };
254
+ });
255
+ return [div(parts.positioner, opts.content())];
256
+ },
257
+ }),
258
+ enter: opts.transition?.enter,
259
+ leave: opts.transition?.leave,
260
+ });
261
+ }
262
+ export const menu = { init, update, connect, overlay };
@@ -0,0 +1,111 @@
1
+ import type { Send } from '@llui/dom';
2
+ /**
3
+ * Navigation menu — multi-level menu bar with hover/focus-triggered
4
+ * submenus. Unlike `menu` (a single dropdown), navigation-menu supports
5
+ * nested submenus arbitrarily deep and is typically used for primary
6
+ * site navigation.
7
+ *
8
+ * State tracks the currently focused item id and the ids of all
9
+ * currently-open branches. The consumer provides the tree structure
10
+ * (items with optional children); the machine doesn't index the
11
+ * hierarchy itself — it just maintains open-paths and lets the view
12
+ * handle traversal.
13
+ *
14
+ * Typical interaction model (delay-based):
15
+ * - Pointer enter on a branch → openBranch after openDelay
16
+ * - Pointer leave of the whole tree → closeAll after closeDelay
17
+ * - Click/keyboard activation → toggleBranch immediately
18
+ *
19
+ * The consumer is responsible for debouncing via setTimeout; the machine
20
+ * just responds to the dispatched messages.
21
+ */
22
+ export interface NavMenuState {
23
+ /** Ids of open branches, in open order (root-first). Closing an
24
+ * ancestor automatically closes its descendants. */
25
+ open: string[];
26
+ focused: string | null;
27
+ disabled: boolean;
28
+ }
29
+ export type NavMenuMsg = {
30
+ type: 'openBranch';
31
+ id: string;
32
+ ancestorIds: string[];
33
+ } | {
34
+ type: 'closeBranch';
35
+ id: string;
36
+ } | {
37
+ type: 'toggleBranch';
38
+ id: string;
39
+ ancestorIds: string[];
40
+ } | {
41
+ type: 'closeAll';
42
+ } | {
43
+ type: 'focus';
44
+ id: string | null;
45
+ };
46
+ export interface NavMenuInit {
47
+ open?: string[];
48
+ focused?: string | null;
49
+ disabled?: boolean;
50
+ }
51
+ export declare function init(opts?: NavMenuInit): NavMenuState;
52
+ export declare function update(state: NavMenuState, msg: NavMenuMsg): [NavMenuState, never[]];
53
+ export declare function isOpen(state: NavMenuState, id: string): boolean;
54
+ export interface NavItemParts<S> {
55
+ trigger: {
56
+ type: 'button';
57
+ role: 'menuitem';
58
+ id: string;
59
+ 'aria-haspopup': 'menu' | undefined;
60
+ 'aria-expanded': (s: S) => boolean | undefined;
61
+ 'data-scope': 'navigation-menu';
62
+ 'data-part': 'trigger';
63
+ 'data-state': (s: S) => 'open' | 'closed';
64
+ 'data-value': string;
65
+ tabIndex: (s: S) => number;
66
+ onClick: (e: MouseEvent) => void;
67
+ onPointerEnter: (e: PointerEvent) => void;
68
+ onFocus: (e: FocusEvent) => void;
69
+ };
70
+ content: {
71
+ role: 'menu';
72
+ id: string;
73
+ 'aria-labelledby': string;
74
+ 'data-scope': 'navigation-menu';
75
+ 'data-part': 'content';
76
+ 'data-state': (s: S) => 'open' | 'closed';
77
+ hidden: (s: S) => boolean;
78
+ };
79
+ }
80
+ export interface NavMenuParts<S> {
81
+ root: {
82
+ role: 'menubar';
83
+ 'aria-label': string;
84
+ 'data-scope': 'navigation-menu';
85
+ 'data-part': 'root';
86
+ 'data-disabled': (s: S) => '' | undefined;
87
+ onPointerLeave: (e: PointerEvent) => void;
88
+ };
89
+ item: (id: string, options: {
90
+ isBranch: boolean;
91
+ ancestorIds?: string[];
92
+ }) => NavItemParts<S>;
93
+ }
94
+ export interface ConnectOptions {
95
+ id: string;
96
+ label?: string;
97
+ /**
98
+ * Whether pointer-leaving the whole menu closes everything. Default: true.
99
+ * The consumer can inject their own close delay by intercepting
100
+ * onPointerLeave + calling setTimeout + dispatching closeAll.
101
+ */
102
+ closeOnLeave?: boolean;
103
+ }
104
+ export declare function connect<S>(get: (s: S) => NavMenuState, send: Send<NavMenuMsg>, opts: ConnectOptions): NavMenuParts<S>;
105
+ export declare const navigationMenu: {
106
+ init: typeof init;
107
+ update: typeof update;
108
+ connect: typeof connect;
109
+ isOpen: typeof isOpen;
110
+ };
111
+ //# sourceMappingURL=navigation-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation-menu.d.ts","sourceRoot":"","sources":["../../src/components/navigation-menu.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ export function init(opts = {}) {
2
+ return {
3
+ open: opts.open ?? [],
4
+ focused: opts.focused ?? null,
5
+ disabled: opts.disabled ?? false,
6
+ };
7
+ }
8
+ export function update(state, msg) {
9
+ if (state.disabled)
10
+ return [state, []];
11
+ switch (msg.type) {
12
+ case 'openBranch': {
13
+ // Close any siblings of `id` at the same ancestor path, then add it.
14
+ // Sibling detection: an entry is a sibling if its ancestor set matches
15
+ // msg.ancestorIds and it isn't msg.id. We don't track ancestors in
16
+ // state, so: filter `open` to keep only entries that are themselves
17
+ // an ancestor of msg.id, plus msg.id.
18
+ const keep = new Set([...msg.ancestorIds, msg.id]);
19
+ const open = state.open.filter((o) => keep.has(o));
20
+ if (!open.includes(msg.id))
21
+ open.push(msg.id);
22
+ return [{ ...state, open }, []];
23
+ }
24
+ case 'closeBranch': {
25
+ // Close this branch and any descendants that follow it in the open
26
+ // list. Since open is ordered root-first, descendants come after.
27
+ const idx = state.open.indexOf(msg.id);
28
+ if (idx === -1)
29
+ return [state, []];
30
+ const open = state.open.slice(0, idx);
31
+ return [{ ...state, open }, []];
32
+ }
33
+ case 'toggleBranch':
34
+ if (state.open.includes(msg.id)) {
35
+ return update(state, { type: 'closeBranch', id: msg.id });
36
+ }
37
+ return update(state, { type: 'openBranch', id: msg.id, ancestorIds: msg.ancestorIds });
38
+ case 'closeAll':
39
+ return [{ ...state, open: [] }, []];
40
+ case 'focus':
41
+ return [{ ...state, focused: msg.id }, []];
42
+ }
43
+ }
44
+ export function isOpen(state, id) {
45
+ return state.open.includes(id);
46
+ }
47
+ export function connect(get, send, opts) {
48
+ const triggerId = (v) => `${opts.id}:trigger:${v}`;
49
+ const contentId = (v) => `${opts.id}:content:${v}`;
50
+ const closeOnLeave = opts.closeOnLeave !== false;
51
+ return {
52
+ root: {
53
+ role: 'menubar',
54
+ 'aria-label': opts.label ?? 'Main navigation',
55
+ 'data-scope': 'navigation-menu',
56
+ 'data-part': 'root',
57
+ 'data-disabled': (s) => (get(s).disabled ? '' : undefined),
58
+ onPointerLeave: () => {
59
+ if (closeOnLeave)
60
+ send({ type: 'closeAll' });
61
+ },
62
+ },
63
+ item: (id, options) => {
64
+ const ancestorIds = options.ancestorIds ?? [];
65
+ return {
66
+ trigger: {
67
+ type: 'button',
68
+ role: 'menuitem',
69
+ id: triggerId(id),
70
+ 'aria-haspopup': options.isBranch ? 'menu' : undefined,
71
+ 'aria-expanded': (s) => (options.isBranch ? isOpen(get(s), id) : undefined),
72
+ 'data-scope': 'navigation-menu',
73
+ 'data-part': 'trigger',
74
+ 'data-state': (s) => (isOpen(get(s), id) ? 'open' : 'closed'),
75
+ 'data-value': id,
76
+ tabIndex: (s) => (get(s).focused === id ? 0 : -1),
77
+ onClick: () => {
78
+ if (options.isBranch) {
79
+ send({ type: 'toggleBranch', id, ancestorIds });
80
+ }
81
+ },
82
+ onPointerEnter: () => {
83
+ if (options.isBranch) {
84
+ send({ type: 'openBranch', id, ancestorIds });
85
+ }
86
+ },
87
+ onFocus: () => send({ type: 'focus', id }),
88
+ },
89
+ content: {
90
+ role: 'menu',
91
+ id: contentId(id),
92
+ 'aria-labelledby': triggerId(id),
93
+ 'data-scope': 'navigation-menu',
94
+ 'data-part': 'content',
95
+ 'data-state': (s) => (isOpen(get(s), id) ? 'open' : 'closed'),
96
+ hidden: (s) => !isOpen(get(s), id),
97
+ },
98
+ };
99
+ },
100
+ };
101
+ }
102
+ export const navigationMenu = { init, update, connect, isOpen };
@@ -0,0 +1,106 @@
1
+ import type { Send } from '@llui/dom';
2
+ /**
3
+ * Number input — numeric field with increment/decrement buttons. Clamps
4
+ * to min/max and snaps to step. Keyboard: Arrow Up/Down, PageUp/PageDown,
5
+ * Home/End.
6
+ */
7
+ export interface NumberInputState {
8
+ value: number | null;
9
+ min: number;
10
+ max: number;
11
+ step: number;
12
+ disabled: boolean;
13
+ readOnly: boolean;
14
+ /** Allow a free-text input value while the user is typing. */
15
+ rawText: string;
16
+ }
17
+ export type NumberInputMsg = {
18
+ type: 'setValue';
19
+ value: number | null;
20
+ } | {
21
+ type: 'setRawText';
22
+ text: string;
23
+ } | {
24
+ type: 'commit';
25
+ } | {
26
+ type: 'increment';
27
+ multiplier?: number;
28
+ } | {
29
+ type: 'decrement';
30
+ multiplier?: number;
31
+ } | {
32
+ type: 'toMin';
33
+ } | {
34
+ type: 'toMax';
35
+ } | {
36
+ type: 'setDisabled';
37
+ disabled: boolean;
38
+ };
39
+ export interface NumberInputInit {
40
+ value?: number | null;
41
+ min?: number;
42
+ max?: number;
43
+ step?: number;
44
+ disabled?: boolean;
45
+ readOnly?: boolean;
46
+ }
47
+ export declare function init(opts?: NumberInputInit): NumberInputState;
48
+ export declare function update(state: NumberInputState, msg: NumberInputMsg): [NumberInputState, never[]];
49
+ export interface NumberInputParts<S> {
50
+ root: {
51
+ 'data-scope': 'number-input';
52
+ 'data-part': 'root';
53
+ 'data-disabled': (s: S) => '' | undefined;
54
+ };
55
+ input: {
56
+ type: 'text';
57
+ role: 'spinbutton';
58
+ inputMode: 'decimal';
59
+ 'aria-valuemin': (s: S) => number | undefined;
60
+ 'aria-valuemax': (s: S) => number | undefined;
61
+ 'aria-valuenow': (s: S) => number | undefined;
62
+ 'aria-disabled': (s: S) => 'true' | undefined;
63
+ 'aria-readonly': (s: S) => 'true' | undefined;
64
+ disabled: (s: S) => boolean;
65
+ readOnly: (s: S) => boolean;
66
+ value: (s: S) => string;
67
+ 'data-scope': 'number-input';
68
+ 'data-part': 'input';
69
+ onInput: (e: Event) => void;
70
+ onBlur: (e: FocusEvent) => void;
71
+ onKeyDown: (e: KeyboardEvent) => void;
72
+ };
73
+ increment: {
74
+ type: 'button';
75
+ 'aria-label': string;
76
+ 'aria-disabled': (s: S) => 'true' | undefined;
77
+ disabled: (s: S) => boolean;
78
+ 'data-scope': 'number-input';
79
+ 'data-part': 'increment';
80
+ tabIndex: -1;
81
+ onClick: (e: MouseEvent) => void;
82
+ };
83
+ decrement: {
84
+ type: 'button';
85
+ 'aria-label': string;
86
+ 'aria-disabled': (s: S) => 'true' | undefined;
87
+ disabled: (s: S) => boolean;
88
+ 'data-scope': 'number-input';
89
+ 'data-part': 'decrement';
90
+ tabIndex: -1;
91
+ onClick: (e: MouseEvent) => void;
92
+ };
93
+ }
94
+ export interface ConnectOptions {
95
+ incrementLabel?: string;
96
+ decrementLabel?: string;
97
+ /** Validate the numeric value before committing. Non-empty array blocks setValue. */
98
+ validate?: (value: number) => string[] | null;
99
+ }
100
+ export declare function connect<S>(get: (s: S) => NumberInputState, send: Send<NumberInputMsg>, opts?: ConnectOptions): NumberInputParts<S>;
101
+ export declare const numberInput: {
102
+ init: typeof init;
103
+ update: typeof update;
104
+ connect: typeof connect;
105
+ };
106
+ //# sourceMappingURL=number-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"number-input.d.ts","sourceRoot":"","sources":["../../src/components/number-input.ts"],"names":[],"mappings":"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"}