@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,131 @@
1
+ export function init(opts = {}) {
2
+ return {
3
+ current: opts.current ?? 0,
4
+ count: opts.count ?? 0,
5
+ loop: opts.loop ?? true,
6
+ autoplay: opts.autoplay ?? false,
7
+ interval: opts.interval ?? 5000,
8
+ paused: false,
9
+ direction: 'forward',
10
+ };
11
+ }
12
+ function clampIndex(state, next) {
13
+ if (state.count === 0)
14
+ return 0;
15
+ if (state.loop)
16
+ return ((next % state.count) + state.count) % state.count;
17
+ return Math.max(0, Math.min(state.count - 1, next));
18
+ }
19
+ export function update(state, msg) {
20
+ switch (msg.type) {
21
+ case 'goTo': {
22
+ const next = clampIndex(state, msg.index);
23
+ return [
24
+ { ...state, current: next, direction: next >= state.current ? 'forward' : 'backward' },
25
+ [],
26
+ ];
27
+ }
28
+ case 'next': {
29
+ const next = clampIndex(state, state.current + 1);
30
+ return [{ ...state, current: next, direction: 'forward' }, []];
31
+ }
32
+ case 'prev': {
33
+ const prev = clampIndex(state, state.current - 1);
34
+ return [{ ...state, current: prev, direction: 'backward' }, []];
35
+ }
36
+ case 'setCount': {
37
+ const current = Math.min(state.current, Math.max(0, msg.count - 1));
38
+ return [{ ...state, count: msg.count, current }, []];
39
+ }
40
+ case 'pause':
41
+ return [{ ...state, paused: true }, []];
42
+ case 'resume':
43
+ return [{ ...state, paused: false }, []];
44
+ case 'setAutoplay':
45
+ return [{ ...state, autoplay: msg.autoplay }, []];
46
+ }
47
+ }
48
+ export function canGoNext(state) {
49
+ if (state.loop)
50
+ return state.count > 0;
51
+ return state.current < state.count - 1;
52
+ }
53
+ export function canGoPrev(state) {
54
+ if (state.loop)
55
+ return state.count > 0;
56
+ return state.current > 0;
57
+ }
58
+ export function connect(get, send, opts) {
59
+ const label = opts.label ?? 'Carousel';
60
+ const indicatorLabel = opts.indicatorLabel ?? 'Slide indicators';
61
+ const nextLabel = opts.nextLabel ?? 'Next slide';
62
+ const prevLabel = opts.prevLabel ?? 'Previous slide';
63
+ const slideLabelFn = opts.slideLabel;
64
+ const slideId = (i) => `${opts.id}:slide:${i}`;
65
+ return {
66
+ root: {
67
+ role: 'region',
68
+ 'aria-roledescription': 'carousel',
69
+ 'aria-label': label,
70
+ 'data-scope': 'carousel',
71
+ 'data-part': 'root',
72
+ 'data-paused': (s) => (get(s).paused ? '' : undefined),
73
+ onPointerEnter: () => send({ type: 'pause' }),
74
+ onPointerLeave: () => send({ type: 'resume' }),
75
+ onFocus: () => send({ type: 'pause' }),
76
+ onBlur: () => send({ type: 'resume' }),
77
+ },
78
+ viewport: {
79
+ 'data-scope': 'carousel',
80
+ 'data-part': 'viewport',
81
+ },
82
+ indicatorGroup: {
83
+ role: 'tablist',
84
+ 'aria-label': indicatorLabel,
85
+ 'data-scope': 'carousel',
86
+ 'data-part': 'indicator-group',
87
+ },
88
+ nextTrigger: {
89
+ type: 'button',
90
+ 'aria-label': nextLabel,
91
+ disabled: (s) => !canGoNext(get(s)),
92
+ 'data-scope': 'carousel',
93
+ 'data-part': 'next-trigger',
94
+ onClick: () => send({ type: 'next' }),
95
+ },
96
+ prevTrigger: {
97
+ type: 'button',
98
+ 'aria-label': prevLabel,
99
+ disabled: (s) => !canGoPrev(get(s)),
100
+ 'data-scope': 'carousel',
101
+ 'data-part': 'prev-trigger',
102
+ onClick: () => send({ type: 'prev' }),
103
+ },
104
+ slide: (index) => ({
105
+ slide: {
106
+ role: 'tabpanel',
107
+ id: slideId(index),
108
+ 'aria-roledescription': 'slide',
109
+ 'aria-label': slideLabelFn ? slideLabelFn(index, 0) : `Slide ${index + 1}`,
110
+ 'data-scope': 'carousel',
111
+ 'data-part': 'slide',
112
+ 'data-index': String(index),
113
+ 'data-active': (s) => (get(s).current === index ? '' : undefined),
114
+ hidden: (s) => get(s).current !== index,
115
+ },
116
+ indicator: {
117
+ type: 'button',
118
+ role: 'tab',
119
+ 'aria-label': `Go to slide ${index + 1}`,
120
+ 'aria-selected': (s) => get(s).current === index,
121
+ 'aria-controls': slideId(index),
122
+ 'data-scope': 'carousel',
123
+ 'data-part': 'indicator',
124
+ 'data-index': String(index),
125
+ 'data-active': (s) => (get(s).current === index ? '' : undefined),
126
+ onClick: () => send({ type: 'goTo', index }),
127
+ },
128
+ }),
129
+ };
130
+ }
131
+ export const carousel = { init, update, connect, canGoNext, canGoPrev };
@@ -0,0 +1,95 @@
1
+ import type { Send } from '@llui/dom';
2
+ /**
3
+ * Cascade select — a series of dependent selects where each level's
4
+ * choice filters the options of the next. Classic example: country
5
+ * → state → city. The machine stores a flat list of selections
6
+ * (one per level, or null) and the options at each level; filtering
7
+ * logic is left to the view/consumer.
8
+ *
9
+ * Level shape: the consumer passes an array of Level descriptors on
10
+ * setLevels, each with its own options. Selecting at level N clears
11
+ * selections at levels > N.
12
+ */
13
+ export interface CascadeLevel {
14
+ id: string;
15
+ label: string;
16
+ options: Array<{
17
+ value: string;
18
+ label: string;
19
+ disabled?: boolean;
20
+ }>;
21
+ }
22
+ export interface CascadeSelectState {
23
+ levels: CascadeLevel[];
24
+ /** Parallel to levels: one value per level, or null. */
25
+ values: (string | null)[];
26
+ disabled: boolean;
27
+ }
28
+ export type CascadeSelectMsg = {
29
+ type: 'setLevels';
30
+ levels: CascadeLevel[];
31
+ } | {
32
+ type: 'setValue';
33
+ levelIndex: number;
34
+ value: string | null;
35
+ } | {
36
+ type: 'clear';
37
+ };
38
+ export interface CascadeSelectInit {
39
+ levels?: CascadeLevel[];
40
+ values?: (string | null)[];
41
+ disabled?: boolean;
42
+ }
43
+ export declare function init(opts?: CascadeSelectInit): CascadeSelectState;
44
+ export declare function update(state: CascadeSelectState, msg: CascadeSelectMsg): [CascadeSelectState, never[]];
45
+ export declare function isLevelReady(state: CascadeSelectState, levelIndex: number): boolean;
46
+ export declare function isComplete(state: CascadeSelectState): boolean;
47
+ export declare function completeValues(state: CascadeSelectState): string[] | null;
48
+ export interface CascadeLevelParts<S> {
49
+ label: {
50
+ for: string;
51
+ 'data-scope': 'cascade-select';
52
+ 'data-part': 'level-label';
53
+ };
54
+ select: {
55
+ id: string;
56
+ disabled: (s: S) => boolean;
57
+ value: (s: S) => string;
58
+ 'data-scope': 'cascade-select';
59
+ 'data-part': 'level-select';
60
+ 'data-level': string;
61
+ 'data-ready': (s: S) => '' | undefined;
62
+ onChange: (e: Event) => void;
63
+ };
64
+ }
65
+ export interface CascadeSelectParts<S> {
66
+ root: {
67
+ 'data-scope': 'cascade-select';
68
+ 'data-part': 'root';
69
+ 'data-disabled': (s: S) => '' | undefined;
70
+ 'data-complete': (s: S) => '' | undefined;
71
+ };
72
+ clearTrigger: {
73
+ type: 'button';
74
+ 'aria-label': string;
75
+ disabled: (s: S) => boolean;
76
+ 'data-scope': 'cascade-select';
77
+ 'data-part': 'clear-trigger';
78
+ onClick: (e: MouseEvent) => void;
79
+ };
80
+ level: (index: number) => CascadeLevelParts<S>;
81
+ }
82
+ export interface ConnectOptions {
83
+ id: string;
84
+ clearLabel?: string;
85
+ }
86
+ export declare function connect<S>(get: (s: S) => CascadeSelectState, send: Send<CascadeSelectMsg>, opts: ConnectOptions): CascadeSelectParts<S>;
87
+ export declare const cascadeSelect: {
88
+ init: typeof init;
89
+ update: typeof update;
90
+ connect: typeof connect;
91
+ isLevelReady: typeof isLevelReady;
92
+ isComplete: typeof isComplete;
93
+ completeValues: typeof completeValues;
94
+ };
95
+ //# sourceMappingURL=cascade-select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cascade-select.d.ts","sourceRoot":"","sources":["../../src/components/cascade-select.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ export function init(opts = {}) {
2
+ const levels = opts.levels ?? [];
3
+ const values = opts.values ?? new Array(levels.length).fill(null);
4
+ // Normalize: pad or trim values array to match levels length
5
+ const normalized = [];
6
+ for (let i = 0; i < levels.length; i++) {
7
+ normalized.push(values[i] ?? null);
8
+ }
9
+ return {
10
+ levels,
11
+ values: normalized,
12
+ disabled: opts.disabled ?? false,
13
+ };
14
+ }
15
+ export function update(state, msg) {
16
+ if (state.disabled && msg.type !== 'clear')
17
+ return [state, []];
18
+ switch (msg.type) {
19
+ case 'setLevels': {
20
+ const values = new Array(msg.levels.length).fill(null);
21
+ return [{ ...state, levels: msg.levels, values }, []];
22
+ }
23
+ case 'setValue': {
24
+ if (msg.levelIndex < 0 || msg.levelIndex >= state.levels.length)
25
+ return [state, []];
26
+ // Setting a level clears all levels below it.
27
+ const next = state.values.slice();
28
+ next[msg.levelIndex] = msg.value;
29
+ for (let i = msg.levelIndex + 1; i < next.length; i++) {
30
+ next[i] = null;
31
+ }
32
+ return [{ ...state, values: next }, []];
33
+ }
34
+ case 'clear':
35
+ return [{ ...state, values: new Array(state.levels.length).fill(null) }, []];
36
+ }
37
+ }
38
+ export function isLevelReady(state, levelIndex) {
39
+ // A level is ready to accept input if all prior levels have values.
40
+ for (let i = 0; i < levelIndex; i++) {
41
+ if (state.values[i] === null)
42
+ return false;
43
+ }
44
+ return true;
45
+ }
46
+ export function isComplete(state) {
47
+ return state.values.every((v) => v !== null);
48
+ }
49
+ export function completeValues(state) {
50
+ if (!isComplete(state))
51
+ return null;
52
+ return state.values;
53
+ }
54
+ export function connect(get, send, opts) {
55
+ const levelId = (i) => `${opts.id}:level:${i}`;
56
+ return {
57
+ root: {
58
+ 'data-scope': 'cascade-select',
59
+ 'data-part': 'root',
60
+ 'data-disabled': (s) => (get(s).disabled ? '' : undefined),
61
+ 'data-complete': (s) => (isComplete(get(s)) ? '' : undefined),
62
+ },
63
+ clearTrigger: {
64
+ type: 'button',
65
+ 'aria-label': opts.clearLabel ?? 'Clear selection',
66
+ disabled: (s) => get(s).values.every((v) => v === null),
67
+ 'data-scope': 'cascade-select',
68
+ 'data-part': 'clear-trigger',
69
+ onClick: () => send({ type: 'clear' }),
70
+ },
71
+ level: (index) => ({
72
+ label: {
73
+ for: levelId(index),
74
+ 'data-scope': 'cascade-select',
75
+ 'data-part': 'level-label',
76
+ },
77
+ select: {
78
+ id: levelId(index),
79
+ disabled: (s) => get(s).disabled || !isLevelReady(get(s), index),
80
+ value: (s) => get(s).values[index] ?? '',
81
+ 'data-scope': 'cascade-select',
82
+ 'data-part': 'level-select',
83
+ 'data-level': String(index),
84
+ 'data-ready': (s) => (isLevelReady(get(s), index) ? '' : undefined),
85
+ onChange: (e) => {
86
+ const el = e.target;
87
+ send({ type: 'setValue', levelIndex: index, value: el.value || null });
88
+ },
89
+ },
90
+ }),
91
+ };
92
+ }
93
+ export const cascadeSelect = {
94
+ init,
95
+ update,
96
+ connect,
97
+ isLevelReady,
98
+ isComplete,
99
+ completeValues,
100
+ };
@@ -0,0 +1,74 @@
1
+ import type { Send } from '@llui/dom';
2
+ /**
3
+ * Checkbox — a tri-state form control (checked / unchecked / indeterminate).
4
+ * The `indeterminate` state is a visual-only state used to represent "partial"
5
+ * selection (e.g. a parent whose children are mixed checked).
6
+ *
7
+ * Rendering typically uses two elements: a visual indicator (the styled box)
8
+ * and a hidden native `<input type="checkbox">` for form participation +
9
+ * accessibility. `connect()` returns props for both.
10
+ */
11
+ export type CheckedState = boolean | 'indeterminate';
12
+ export interface CheckboxState {
13
+ checked: CheckedState;
14
+ disabled: boolean;
15
+ required: boolean;
16
+ }
17
+ export type CheckboxMsg = {
18
+ type: 'toggle';
19
+ } | {
20
+ type: 'setChecked';
21
+ checked: CheckedState;
22
+ } | {
23
+ type: 'setDisabled';
24
+ disabled: boolean;
25
+ };
26
+ export interface CheckboxInit {
27
+ checked?: CheckedState;
28
+ disabled?: boolean;
29
+ required?: boolean;
30
+ }
31
+ export declare function init(opts?: CheckboxInit): CheckboxState;
32
+ export declare function update(state: CheckboxState, msg: CheckboxMsg): [CheckboxState, never[]];
33
+ export interface CheckboxParts<S> {
34
+ /** The visual box/container — `role="checkbox"` for accessibility. */
35
+ root: {
36
+ role: 'checkbox';
37
+ 'aria-checked': (s: S) => 'true' | 'false' | 'mixed';
38
+ 'aria-disabled': (s: S) => 'true' | undefined;
39
+ 'aria-required': (s: S) => 'true' | undefined;
40
+ 'data-state': (s: S) => 'checked' | 'unchecked' | 'indeterminate';
41
+ 'data-disabled': (s: S) => '' | undefined;
42
+ 'data-scope': 'checkbox';
43
+ 'data-part': 'root';
44
+ tabIndex: (s: S) => number;
45
+ onClick: (e: MouseEvent) => void;
46
+ onKeyDown: (e: KeyboardEvent) => void;
47
+ };
48
+ /** A native hidden input for form participation. */
49
+ hiddenInput: {
50
+ type: 'checkbox';
51
+ 'aria-hidden': 'true';
52
+ tabIndex: -1;
53
+ style: string;
54
+ checked: (s: S) => boolean;
55
+ indeterminate: (s: S) => boolean;
56
+ disabled: (s: S) => boolean;
57
+ required: (s: S) => boolean;
58
+ 'data-scope': 'checkbox';
59
+ 'data-part': 'hidden-input';
60
+ };
61
+ /** Optional indicator child (the checkmark). */
62
+ indicator: {
63
+ 'data-state': (s: S) => 'checked' | 'unchecked' | 'indeterminate';
64
+ 'data-scope': 'checkbox';
65
+ 'data-part': 'indicator';
66
+ };
67
+ }
68
+ export declare function connect<S>(get: (s: S) => CheckboxState, send: Send<CheckboxMsg>): CheckboxParts<S>;
69
+ export declare const checkbox: {
70
+ init: typeof init;
71
+ update: typeof update;
72
+ connect: typeof connect;
73
+ };
74
+ //# sourceMappingURL=checkbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../src/components/checkbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC;;;;;;;;GAQG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,eAAe,CAAA;AAEpD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,YAAY,CAAA;IACrB,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAA;AAE9C,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,wBAAgB,IAAI,CAAC,IAAI,GAAE,YAAiB,GAAG,aAAa,CAM3D;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,WAAW,GAAG,CAAC,aAAa,EAAE,KAAK,EAAE,CAAC,CAYvF;AAYD,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,sEAAsE;IACtE,IAAI,EAAE;QACJ,IAAI,EAAE,UAAU,CAAA;QAChB,cAAc,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;QACpD,eAAe,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAA;QAC7C,eAAe,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAA;QAC7C,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,SAAS,GAAG,WAAW,GAAG,eAAe,CAAA;QACjE,eAAe,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,SAAS,CAAA;QACzC,YAAY,EAAE,UAAU,CAAA;QACxB,WAAW,EAAE,MAAM,CAAA;QACnB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;QAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;QAChC,SAAS,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;KACtC,CAAA;IACD,oDAAoD;IACpD,WAAW,EAAE;QACX,IAAI,EAAE,UAAU,CAAA;QAChB,aAAa,EAAE,MAAM,CAAA;QACrB,QAAQ,EAAE,CAAC,CAAC,CAAA;QACZ,KAAK,EAAE,MAAM,CAAA;QACb,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;QAC1B,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;QAChC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;QAC3B,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;QAC3B,YAAY,EAAE,UAAU,CAAA;QACxB,WAAW,EAAE,cAAc,CAAA;KAC5B,CAAA;IACD,gDAAgD;IAChD,SAAS,EAAE;QACT,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,SAAS,GAAG,WAAW,GAAG,eAAe,CAAA;QACjE,YAAY,EAAE,UAAU,CAAA;QACxB,WAAW,EAAE,WAAW,CAAA;KACzB,CAAA;CACF;AAKD,wBAAgB,OAAO,CAAC,CAAC,EACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,aAAa,EAC5B,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,GACtB,aAAa,CAAC,CAAC,CAAC,CAsClB;AAED,eAAO,MAAM,QAAQ;;;;CAA4B,CAAA"}
@@ -0,0 +1,73 @@
1
+ export function init(opts = {}) {
2
+ return {
3
+ checked: opts.checked ?? false,
4
+ disabled: opts.disabled ?? false,
5
+ required: opts.required ?? false,
6
+ };
7
+ }
8
+ export function update(state, msg) {
9
+ switch (msg.type) {
10
+ case 'toggle':
11
+ if (state.disabled)
12
+ return [state, []];
13
+ // Tri-state toggle: indeterminate → checked, otherwise flip boolean
14
+ if (state.checked === 'indeterminate')
15
+ return [{ ...state, checked: true }, []];
16
+ return [{ ...state, checked: !state.checked }, []];
17
+ case 'setChecked':
18
+ return [{ ...state, checked: msg.checked }, []];
19
+ case 'setDisabled':
20
+ return [{ ...state, disabled: msg.disabled }, []];
21
+ }
22
+ }
23
+ function ariaChecked(c) {
24
+ if (c === 'indeterminate')
25
+ return 'mixed';
26
+ return c ? 'true' : 'false';
27
+ }
28
+ function dataState(c) {
29
+ if (c === 'indeterminate')
30
+ return 'indeterminate';
31
+ return c ? 'checked' : 'unchecked';
32
+ }
33
+ const HIDDEN_STYLE = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;';
34
+ export function connect(get, send) {
35
+ return {
36
+ root: {
37
+ role: 'checkbox',
38
+ 'aria-checked': (s) => ariaChecked(get(s).checked),
39
+ 'aria-disabled': (s) => (get(s).disabled ? 'true' : undefined),
40
+ 'aria-required': (s) => (get(s).required ? 'true' : undefined),
41
+ 'data-state': (s) => dataState(get(s).checked),
42
+ 'data-disabled': (s) => (get(s).disabled ? '' : undefined),
43
+ 'data-scope': 'checkbox',
44
+ 'data-part': 'root',
45
+ tabIndex: (s) => (get(s).disabled ? -1 : 0),
46
+ onClick: () => send({ type: 'toggle' }),
47
+ onKeyDown: (e) => {
48
+ if (e.key === ' ') {
49
+ e.preventDefault();
50
+ send({ type: 'toggle' });
51
+ }
52
+ },
53
+ },
54
+ hiddenInput: {
55
+ type: 'checkbox',
56
+ 'aria-hidden': 'true',
57
+ tabIndex: -1,
58
+ style: HIDDEN_STYLE,
59
+ checked: (s) => get(s).checked === true,
60
+ indeterminate: (s) => get(s).checked === 'indeterminate',
61
+ disabled: (s) => get(s).disabled,
62
+ required: (s) => get(s).required,
63
+ 'data-scope': 'checkbox',
64
+ 'data-part': 'hidden-input',
65
+ },
66
+ indicator: {
67
+ 'data-state': (s) => dataState(get(s).checked),
68
+ 'data-scope': 'checkbox',
69
+ 'data-part': 'indicator',
70
+ },
71
+ };
72
+ }
73
+ export const checkbox = { init, update, connect };
@@ -0,0 +1,72 @@
1
+ import type { Send } from '@llui/dom';
2
+ /**
3
+ * Clipboard — copy-to-clipboard with transient "copied" feedback. The
4
+ * actual clipboard write is performed by the consumer via an effect (or
5
+ * inline in the trigger's onClick handler). Reducer tracks the success
6
+ * state flag and an auto-reset timestamp.
7
+ */
8
+ export interface ClipboardState {
9
+ value: string;
10
+ copied: boolean;
11
+ }
12
+ export type ClipboardMsg = {
13
+ type: 'setValue';
14
+ value: string;
15
+ } | {
16
+ type: 'copy';
17
+ } | {
18
+ type: 'copied';
19
+ } | {
20
+ type: 'reset';
21
+ };
22
+ export interface ClipboardInit {
23
+ value?: string;
24
+ }
25
+ export declare function init(opts?: ClipboardInit): ClipboardState;
26
+ export declare function update(state: ClipboardState, msg: ClipboardMsg): [ClipboardState, never[]];
27
+ /**
28
+ * Attempt to copy the value to the clipboard. Returns a Promise that resolves
29
+ * on success. Consumer dispatches `copied` or `reset` based on the result.
30
+ */
31
+ export declare function copyToClipboard(value: string): Promise<void>;
32
+ export interface ClipboardParts<S> {
33
+ root: {
34
+ 'data-scope': 'clipboard';
35
+ 'data-part': 'root';
36
+ 'data-copied': (s: S) => '' | undefined;
37
+ };
38
+ trigger: {
39
+ type: 'button';
40
+ 'aria-label': string;
41
+ 'data-scope': 'clipboard';
42
+ 'data-part': 'trigger';
43
+ 'data-copied': (s: S) => '' | undefined;
44
+ onClick: (e: MouseEvent) => void;
45
+ };
46
+ input: {
47
+ type: 'text';
48
+ readOnly: true;
49
+ value: (s: S) => string;
50
+ 'data-scope': 'clipboard';
51
+ 'data-part': 'input';
52
+ onFocus: (e: FocusEvent) => void;
53
+ };
54
+ indicator: {
55
+ 'data-scope': 'clipboard';
56
+ 'data-part': 'indicator';
57
+ 'data-copied': (s: S) => '' | undefined;
58
+ 'aria-live': 'polite';
59
+ };
60
+ }
61
+ export interface ConnectOptions {
62
+ copyLabel?: string;
63
+ onCopy?: (value: string) => void;
64
+ }
65
+ export declare function connect<S>(get: (s: S) => ClipboardState, send: Send<ClipboardMsg>, opts?: ConnectOptions): ClipboardParts<S>;
66
+ export declare const clipboard: {
67
+ init: typeof init;
68
+ update: typeof update;
69
+ connect: typeof connect;
70
+ copyToClipboard: typeof copyToClipboard;
71
+ };
72
+ //# sourceMappingURL=clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/components/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,IAAI,CAAC,IAAI,GAAE,aAAkB,GAAG,cAAc,CAE7D;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,GAAG,CAAC,cAAc,EAAE,KAAK,EAAE,CAAC,CAU1F;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBlE;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE;QACJ,YAAY,EAAE,WAAW,CAAA;QACzB,WAAW,EAAE,MAAM,CAAA;QACnB,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,SAAS,CAAA;KACxC,CAAA;IACD,OAAO,EAAE;QACP,IAAI,EAAE,QAAQ,CAAA;QACd,YAAY,EAAE,MAAM,CAAA;QACpB,YAAY,EAAE,WAAW,CAAA;QACzB,WAAW,EAAE,SAAS,CAAA;QACtB,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,SAAS,CAAA;QACvC,OAAO,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;KACjC,CAAA;IACD,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAA;QACZ,QAAQ,EAAE,IAAI,CAAA;QACd,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;QACvB,YAAY,EAAE,WAAW,CAAA;QACzB,WAAW,EAAE,OAAO,CAAA;QACpB,OAAO,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;KACjC,CAAA;IACD,SAAS,EAAE;QACT,YAAY,EAAE,WAAW,CAAA;QACzB,WAAW,EAAE,WAAW,CAAA;QACxB,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,SAAS,CAAA;QACvC,WAAW,EAAE,QAAQ,CAAA;KACtB,CAAA;CACF;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC;AAED,wBAAgB,OAAO,CAAC,CAAC,EACvB,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,cAAc,EAC7B,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,EACxB,IAAI,GAAE,cAAmB,GACxB,cAAc,CAAC,CAAC,CAAC,CAkCnB;AAED,eAAO,MAAM,SAAS;;;;;CAA6C,CAAA"}
@@ -0,0 +1,73 @@
1
+ export function init(opts = {}) {
2
+ return { value: opts.value ?? '', copied: false };
3
+ }
4
+ export function update(state, msg) {
5
+ switch (msg.type) {
6
+ case 'setValue':
7
+ return [{ ...state, value: msg.value, copied: false }, []];
8
+ case 'copy':
9
+ case 'copied':
10
+ return [{ ...state, copied: true }, []];
11
+ case 'reset':
12
+ return [{ ...state, copied: false }, []];
13
+ }
14
+ }
15
+ /**
16
+ * Attempt to copy the value to the clipboard. Returns a Promise that resolves
17
+ * on success. Consumer dispatches `copied` or `reset` based on the result.
18
+ */
19
+ export async function copyToClipboard(value) {
20
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
21
+ await navigator.clipboard.writeText(value);
22
+ return;
23
+ }
24
+ // Fallback: ephemeral textarea
25
+ const textarea = document.createElement('textarea');
26
+ textarea.value = value;
27
+ textarea.style.position = 'fixed';
28
+ textarea.style.opacity = '0';
29
+ document.body.appendChild(textarea);
30
+ textarea.select();
31
+ try {
32
+ document.execCommand('copy');
33
+ }
34
+ finally {
35
+ document.body.removeChild(textarea);
36
+ }
37
+ }
38
+ export function connect(get, send, opts = {}) {
39
+ const copyLabel = opts.copyLabel ?? 'Copy to clipboard';
40
+ return {
41
+ root: {
42
+ 'data-scope': 'clipboard',
43
+ 'data-part': 'root',
44
+ 'data-copied': (s) => (get(s).copied ? '' : undefined),
45
+ },
46
+ trigger: {
47
+ type: 'button',
48
+ 'aria-label': copyLabel,
49
+ 'data-scope': 'clipboard',
50
+ 'data-part': 'trigger',
51
+ 'data-copied': (s) => (get(s).copied ? '' : undefined),
52
+ onClick: () => {
53
+ send({ type: 'copy' });
54
+ opts.onCopy?.('');
55
+ },
56
+ },
57
+ input: {
58
+ type: 'text',
59
+ readOnly: true,
60
+ value: (s) => get(s).value,
61
+ 'data-scope': 'clipboard',
62
+ 'data-part': 'input',
63
+ onFocus: (e) => e.currentTarget.select(),
64
+ },
65
+ indicator: {
66
+ 'data-scope': 'clipboard',
67
+ 'data-part': 'indicator',
68
+ 'data-copied': (s) => (get(s).copied ? '' : undefined),
69
+ 'aria-live': 'polite',
70
+ },
71
+ };
72
+ }
73
+ export const clipboard = { init, update, connect, copyToClipboard };