juxscript 1.1.404 → 1.1.409

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 (154) hide show
  1. package/dist/components/button.d.ts +1 -0
  2. package/dist/components/button.d.ts.map +1 -1
  3. package/dist/components/button.js +38 -0
  4. package/dist/components/button.js.map +1 -1
  5. package/dist/components/c.d.ts +53 -0
  6. package/dist/components/c.d.ts.map +1 -0
  7. package/dist/components/c.js +127 -0
  8. package/dist/components/c.js.map +1 -0
  9. package/dist/components/charts/barChart.d.ts +119 -0
  10. package/dist/components/charts/barChart.d.ts.map +1 -0
  11. package/dist/components/charts/barChart.js +644 -0
  12. package/dist/components/charts/barChart.js.map +1 -0
  13. package/dist/components/charts/lineChart.d.ts +104 -0
  14. package/dist/components/charts/lineChart.d.ts.map +1 -0
  15. package/dist/components/charts/lineChart.js +466 -0
  16. package/dist/components/charts/lineChart.js.map +1 -0
  17. package/dist/components/charts/pieChart.d.ts +93 -0
  18. package/dist/components/charts/pieChart.d.ts.map +1 -0
  19. package/dist/components/charts/pieChart.js +397 -0
  20. package/dist/components/charts/pieChart.js.map +1 -0
  21. package/dist/components/checkbox.d.ts +2 -0
  22. package/dist/components/checkbox.d.ts.map +1 -1
  23. package/dist/components/checkbox.js +47 -0
  24. package/dist/components/checkbox.js.map +1 -1
  25. package/dist/components/flex.d.ts +91 -0
  26. package/dist/components/flex.d.ts.map +1 -0
  27. package/dist/components/flex.js +166 -0
  28. package/dist/components/flex.js.map +1 -0
  29. package/dist/components/g.d.ts +21 -0
  30. package/dist/components/g.d.ts.map +1 -0
  31. package/dist/components/g.js +52 -0
  32. package/dist/components/g.js.map +1 -0
  33. package/dist/components/input.d.ts +2 -0
  34. package/dist/components/input.d.ts.map +1 -1
  35. package/dist/components/input.js +21 -2
  36. package/dist/components/input.js.map +1 -1
  37. package/dist/components/jtable.d.ts +47 -0
  38. package/dist/components/jtable.d.ts.map +1 -0
  39. package/dist/components/jtable.js +307 -0
  40. package/dist/components/jtable.js.map +1 -0
  41. package/dist/components/link.d.ts +1 -0
  42. package/dist/components/link.d.ts.map +1 -1
  43. package/dist/components/link.js +17 -0
  44. package/dist/components/link.js.map +1 -1
  45. package/dist/components/list.d.ts +1 -0
  46. package/dist/components/list.d.ts.map +1 -1
  47. package/dist/components/list.js +18 -0
  48. package/dist/components/list.js.map +1 -1
  49. package/dist/components/menu.d.ts +108 -0
  50. package/dist/components/menu.d.ts.map +1 -0
  51. package/dist/components/menu.js +665 -0
  52. package/dist/components/menu.js.map +1 -0
  53. package/dist/components/nav.d.ts +1 -0
  54. package/dist/components/nav.d.ts.map +1 -1
  55. package/dist/components/nav.js +19 -0
  56. package/dist/components/nav.js.map +1 -1
  57. package/dist/components/radio.d.ts +1 -0
  58. package/dist/components/radio.d.ts.map +1 -1
  59. package/dist/components/radio.js +23 -0
  60. package/dist/components/radio.js.map +1 -1
  61. package/dist/components/routes.d.ts +17 -0
  62. package/dist/components/routes.d.ts.map +1 -1
  63. package/dist/components/routes.js +86 -0
  64. package/dist/components/routes.js.map +1 -1
  65. package/dist/components/select.d.ts +1 -0
  66. package/dist/components/select.d.ts.map +1 -1
  67. package/dist/components/select.js +17 -0
  68. package/dist/components/select.js.map +1 -1
  69. package/dist/components/table.d.ts +1 -0
  70. package/dist/components/table.d.ts.map +1 -1
  71. package/dist/components/table.js +20 -0
  72. package/dist/components/table.js.map +1 -1
  73. package/dist/components/tabs.d.ts +17 -1
  74. package/dist/components/tabs.d.ts.map +1 -1
  75. package/dist/components/tabs.js +50 -8
  76. package/dist/components/tabs.js.map +1 -1
  77. package/dist/components/tag.d.ts +1 -0
  78. package/dist/components/tag.d.ts.map +1 -1
  79. package/dist/components/tag.js +16 -0
  80. package/dist/components/tag.js.map +1 -1
  81. package/dist/components/widgets/calendar.d.ts +74 -0
  82. package/dist/components/widgets/calendar.d.ts.map +1 -0
  83. package/dist/components/widgets/calendar.js +308 -0
  84. package/dist/components/widgets/calendar.js.map +1 -0
  85. package/dist/components/widgets/canvas-ai.d.ts +12 -0
  86. package/dist/components/widgets/canvas-ai.d.ts.map +1 -0
  87. package/dist/components/widgets/canvas-ai.js +97 -0
  88. package/dist/components/widgets/canvas-ai.js.map +1 -0
  89. package/dist/components/widgets/canvas-compile.d.ts +36 -0
  90. package/dist/components/widgets/canvas-compile.d.ts.map +1 -0
  91. package/dist/components/widgets/canvas-compile.js +379 -0
  92. package/dist/components/widgets/canvas-compile.js.map +1 -0
  93. package/dist/components/widgets/canvas-persist.d.ts +11 -0
  94. package/dist/components/widgets/canvas-persist.d.ts.map +1 -0
  95. package/dist/components/widgets/canvas-persist.js +60 -0
  96. package/dist/components/widgets/canvas-persist.js.map +1 -0
  97. package/dist/components/widgets/canvas-registry.d.ts +42 -0
  98. package/dist/components/widgets/canvas-registry.d.ts.map +1 -0
  99. package/dist/components/widgets/canvas-registry.js +338 -0
  100. package/dist/components/widgets/canvas-registry.js.map +1 -0
  101. package/dist/components/widgets/canvas-styles.d.ts +2 -0
  102. package/dist/components/widgets/canvas-styles.d.ts.map +1 -0
  103. package/dist/components/widgets/canvas-styles.js +215 -0
  104. package/dist/components/widgets/canvas-styles.js.map +1 -0
  105. package/dist/components/widgets/canvas.d.ts +125 -0
  106. package/dist/components/widgets/canvas.d.ts.map +1 -0
  107. package/dist/components/widgets/canvas.js +1359 -0
  108. package/dist/components/widgets/canvas.js.map +1 -0
  109. package/dist/components/widgets/sidebar.d.ts +100 -0
  110. package/dist/components/widgets/sidebar.d.ts.map +1 -0
  111. package/dist/components/widgets/sidebar.js +434 -0
  112. package/dist/components/widgets/sidebar.js.map +1 -0
  113. package/dist/components/widgets/stepper.d.ts +87 -0
  114. package/dist/components/widgets/stepper.d.ts.map +1 -0
  115. package/dist/components/widgets/stepper.js +388 -0
  116. package/dist/components/widgets/stepper.js.map +1 -0
  117. package/dist/generated/jux-registry.d.ts +24 -0
  118. package/dist/generated/jux-registry.d.ts.map +1 -0
  119. package/dist/generated/jux-registry.js +90 -0
  120. package/dist/generated/jux-registry.js.map +1 -0
  121. package/dist/index.d.ts +39 -23
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +38 -24
  124. package/dist/index.js.map +1 -1
  125. package/dist/state/pageState.d.ts +6 -0
  126. package/dist/state/pageState.d.ts.map +1 -1
  127. package/dist/state/pageState.js +24 -16
  128. package/dist/state/pageState.js.map +1 -1
  129. package/dist/styles/layout-regions-observer.d.ts +7 -0
  130. package/dist/styles/layout-regions-observer.d.ts.map +1 -0
  131. package/dist/styles/layout-regions-observer.js +52 -0
  132. package/dist/styles/layout-regions-observer.js.map +1 -0
  133. package/dist/utils/colors.d.ts +0 -3
  134. package/dist/utils/colors.d.ts.map +1 -1
  135. package/dist/utils/colors.js +20 -6
  136. package/dist/utils/colors.js.map +1 -1
  137. package/dist/utils/resolveContent.d.ts +11 -0
  138. package/dist/utils/resolveContent.d.ts.map +1 -0
  139. package/dist/utils/resolveContent.js +37 -0
  140. package/dist/utils/resolveContent.js.map +1 -0
  141. package/dist/utils/theme.d.ts +58 -0
  142. package/dist/utils/theme.d.ts.map +1 -0
  143. package/dist/utils/theme.js +172 -0
  144. package/dist/utils/theme.js.map +1 -0
  145. package/dist/widgets/canvas.d.ts +3 -69
  146. package/dist/widgets/canvas.d.ts.map +1 -1
  147. package/dist/widgets/canvas.js +3 -791
  148. package/dist/widgets/canvas.js.map +1 -1
  149. package/juxconfig.example.js +19 -0
  150. package/machinery/compiler4.js +103 -9
  151. package/machinery/errors-client.js +171 -67
  152. package/machinery/jux-errors.js +218 -0
  153. package/machinery/serve.js +67 -0
  154. package/package.json +1 -1
@@ -0,0 +1,665 @@
1
+ import { pageState } from '../state/pageState.js';
2
+ import generateId from '../utils/idgen.js';
3
+ let menuStylesInjected = false;
4
+ function injectMenuStyles() {
5
+ if (menuStylesInjected)
6
+ return;
7
+ menuStylesInjected = true;
8
+ const s = document.createElement('style');
9
+ s.id = 'jux-menu-styles';
10
+ s.textContent = `
11
+ .jux-menu {
12
+ --menu-bg: hsl(var(--card,0 0% 100%));
13
+ --menu-border: hsl(var(--border,220 13% 91%));
14
+ --menu-fg: hsl(var(--foreground,220 13% 18%));
15
+ --menu-muted: hsl(var(--muted-foreground,220 9% 46%));
16
+ --menu-hover: hsl(var(--accent,217 91% 97%));
17
+ --menu-active-bg: hsl(var(--accent,217 91% 97%));
18
+ --menu-active-fg: hsl(var(--accent-foreground,217 89% 41%));
19
+ --menu-radius: var(--radius,12px);
20
+ --menu-width: 240px;
21
+ display: flex;
22
+ flex-direction: column;
23
+ width: var(--menu-width);
24
+ background: var(--menu-bg);
25
+ border-right: 1px solid var(--menu-border);
26
+ font-family: var(--font-sans,'Google Sans','Inter',system-ui,sans-serif);
27
+ font-size: 14px;
28
+ overflow: hidden;
29
+ flex-shrink: 0;
30
+ height: 100%;
31
+ box-sizing: border-box;
32
+ transition: width 0.2s ease;
33
+ user-select: none;
34
+ }
35
+ .jux-menu--collapsed {
36
+ --menu-width: 52px;
37
+ }
38
+ .jux-menu__header {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 8px;
42
+ padding: 14px 12px 10px;
43
+ border-bottom: 1px solid var(--menu-border);
44
+ min-height: 52px;
45
+ flex-shrink: 0;
46
+ overflow: hidden;
47
+ }
48
+ .jux-menu__header-icon {
49
+ font-size: 18px;
50
+ flex-shrink: 0;
51
+ width: 28px;
52
+ text-align: center;
53
+ }
54
+ .jux-menu__header-title {
55
+ font-weight: 700;
56
+ font-size: 15px;
57
+ letter-spacing: -0.01em;
58
+ white-space: nowrap;
59
+ overflow: hidden;
60
+ text-overflow: ellipsis;
61
+ flex: 1;
62
+ transition: opacity 0.15s, width 0.2s;
63
+ }
64
+ .jux-menu--collapsed .jux-menu__header-title {
65
+ opacity: 0;
66
+ width: 0;
67
+ flex: 0;
68
+ pointer-events: none;
69
+ }
70
+ .jux-menu--collapsed .jux-menu__header-icon {
71
+ opacity: 0;
72
+ width: 0;
73
+ flex: 0;
74
+ pointer-events: none;
75
+ }
76
+ .jux-menu--collapsed .jux-menu__header {
77
+ justify-content: center;
78
+ padding: 14px 4px 10px;
79
+ }
80
+ .jux-menu__collapse-btn {
81
+ background: none;
82
+ border: none;
83
+ cursor: pointer;
84
+ color: var(--menu-muted);
85
+ padding: 4px;
86
+ border-radius: 4px;
87
+ font-size: 14px;
88
+ line-height: 1;
89
+ flex-shrink: 0;
90
+ transition: color 0.12s, background 0.12s;
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ min-width: 24px;
95
+ min-height: 24px;
96
+ }
97
+ .jux-menu__collapse-btn:hover {
98
+ color: var(--menu-fg);
99
+ background: var(--menu-hover);
100
+ }
101
+ .jux-menu__scroll {
102
+ flex: 1;
103
+ overflow-y: auto;
104
+ overflow-x: hidden;
105
+ padding: 6px 0;
106
+ scrollbar-width: thin;
107
+ scrollbar-color: var(--menu-border) transparent;
108
+ }
109
+ .jux-menu__scroll::-webkit-scrollbar { width: 4px; }
110
+ .jux-menu__scroll::-webkit-scrollbar-track { background: transparent; }
111
+ .jux-menu__scroll::-webkit-scrollbar-thumb { background: var(--menu-border); border-radius: 4px; }
112
+ .jux-menu__footer {
113
+ border-top: 1px solid var(--menu-border);
114
+ padding: 6px 0;
115
+ flex-shrink: 0;
116
+ }
117
+
118
+ /* ── Partition (horizontal rule with optional label) ── */
119
+ .jux-menu__partition {
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 8px;
123
+ padding: 8px 12px 4px;
124
+ color: var(--menu-muted);
125
+ font-size: 11px;
126
+ font-weight: 600;
127
+ text-transform: uppercase;
128
+ letter-spacing: 0.06em;
129
+ white-space: nowrap;
130
+ }
131
+ .jux-menu__partition::before,
132
+ .jux-menu__partition::after {
133
+ content: '';
134
+ flex: 1;
135
+ height: 1px;
136
+ background: var(--menu-border);
137
+ }
138
+ .jux-menu__partition:not(:has(span))::after { display: none; }
139
+ .jux-menu--collapsed .jux-menu__partition {
140
+ padding: 8px 4px 4px;
141
+ justify-content: center;
142
+ }
143
+ .jux-menu--collapsed .jux-menu__partition span { display: none; }
144
+
145
+ /* ── Spacer ── */
146
+ .jux-menu__spacer { height: 8px; }
147
+
148
+ /* ── Group label ── */
149
+ .jux-menu__group-label {
150
+ padding: 8px 12px 2px;
151
+ font-size: 11px;
152
+ font-weight: 600;
153
+ text-transform: uppercase;
154
+ letter-spacing: 0.06em;
155
+ color: var(--menu-muted);
156
+ white-space: nowrap;
157
+ overflow: hidden;
158
+ }
159
+ .jux-menu--collapsed .jux-menu__group-label { display: none; }
160
+
161
+ /* ── Item ── */
162
+ .jux-menu__item {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: 10px;
166
+ padding: 7px 12px;
167
+ margin: 1px 6px;
168
+ border-radius: var(--menu-radius);
169
+ color: var(--menu-fg);
170
+ cursor: pointer;
171
+ text-decoration: none;
172
+ border: none;
173
+ background: none;
174
+ font: inherit;
175
+ font-size: 14px;
176
+ text-align: left;
177
+ width: calc(100% - 12px);
178
+ box-sizing: border-box;
179
+ transition: background 0.1s, color 0.1s;
180
+ position: relative;
181
+ white-space: nowrap;
182
+ min-height: 36px;
183
+ }
184
+ .jux-menu__item:hover:not(.jux-menu__item--disabled) {
185
+ background: var(--menu-hover);
186
+ }
187
+ .jux-menu__item--active {
188
+ background: var(--menu-active-bg);
189
+ color: var(--menu-active-fg);
190
+ font-weight: 600;
191
+ }
192
+ .jux-menu__item--active::before {
193
+ content: '';
194
+ position: absolute;
195
+ left: -6px;
196
+ top: 50%;
197
+ transform: translateY(-50%);
198
+ width: 3px;
199
+ height: 60%;
200
+ background: hsl(var(--primary,217 89% 51%));
201
+ border-radius: 0 2px 2px 0;
202
+ }
203
+ .jux-menu__item--disabled {
204
+ opacity: 0.4;
205
+ cursor: not-allowed;
206
+ }
207
+ .jux-menu__item--has-children > .jux-menu__item-chevron {
208
+ margin-left: auto;
209
+ flex-shrink: 0;
210
+ transition: transform 0.15s;
211
+ color: var(--menu-muted);
212
+ font-size: 11px;
213
+ }
214
+ .jux-menu__item--open > .jux-menu__item-chevron {
215
+ transform: rotate(90deg);
216
+ }
217
+
218
+ /* ── Item parts ── */
219
+ .jux-menu__item-icon {
220
+ font-size: 16px;
221
+ width: 20px;
222
+ text-align: center;
223
+ flex-shrink: 0;
224
+ line-height: 1;
225
+ }
226
+ .jux-menu__item-label {
227
+ flex: 1;
228
+ overflow: hidden;
229
+ text-overflow: ellipsis;
230
+ }
231
+ .jux-menu__item-badge {
232
+ font-size: 11px;
233
+ font-weight: 600;
234
+ padding: 1px 6px;
235
+ border-radius: 999px;
236
+ background: hsl(var(--muted,220 14% 96%));
237
+ color: hsl(var(--muted-foreground,220 9% 46%));
238
+ flex-shrink: 0;
239
+ line-height: 1.4;
240
+ }
241
+ .jux-menu__item-badge--success { background: hsl(142 60% 90%); color: hsl(142 60% 30%); }
242
+ .jux-menu__item-badge--warning { background: hsl(38 90% 90%); color: hsl(38 90% 35%); }
243
+ .jux-menu__item-badge--danger { background: hsl(0 80% 92%); color: hsl(0 72% 40%); }
244
+
245
+ /* ── Submenu ── */
246
+ .jux-menu__submenu {
247
+ overflow: hidden;
248
+ max-height: 0;
249
+ transition: max-height 0.2s ease;
250
+ }
251
+ .jux-menu__submenu--open {
252
+ max-height: 600px;
253
+ }
254
+ .jux-menu__submenu .jux-menu__item {
255
+ padding-left: 40px;
256
+ }
257
+ .jux-menu__submenu .jux-menu__submenu .jux-menu__item {
258
+ padding-left: 56px;
259
+ }
260
+
261
+ /* ── Collapsed icon-rail mode ── */
262
+ .jux-menu--collapsed .jux-menu__item {
263
+ padding: 7px;
264
+ margin: 1px 6px;
265
+ width: calc(100% - 12px);
266
+ justify-content: center;
267
+ }
268
+ .jux-menu--collapsed .jux-menu__item-label,
269
+ .jux-menu--collapsed .jux-menu__item-badge,
270
+ .jux-menu--collapsed .jux-menu__item-chevron { display: none; }
271
+ .jux-menu--collapsed .jux-menu__item-icon {
272
+ width: auto;
273
+ font-size: 18px;
274
+ }
275
+ .jux-menu--collapsed .jux-menu__submenu { display: none; }
276
+
277
+ /* Tooltip on collapsed rail */
278
+ .jux-menu--collapsed .jux-menu__item[data-label]:hover::after {
279
+ content: attr(data-label);
280
+ position: fixed;
281
+ left: 60px;
282
+ background: hsl(var(--popover,220 13% 18%));
283
+ color: hsl(var(--popover-foreground,0 0% 98%));
284
+ padding: 4px 10px;
285
+ border-radius: var(--menu-radius);
286
+ font-size: 13px;
287
+ font-weight: 500;
288
+ white-space: nowrap;
289
+ z-index: 9999;
290
+ pointer-events: none;
291
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
292
+ }
293
+ `;
294
+ document.head.appendChild(s);
295
+ }
296
+ // ── Helpers ──────────────────────────────────────────────────────────────────
297
+ function resolveTarget(target) {
298
+ if (!target)
299
+ return document.getElementById('app');
300
+ return document.getElementById(target) || document.querySelector(target);
301
+ }
302
+ function isActive(item, activeId) {
303
+ if (item.active)
304
+ return true;
305
+ if (item.id && item.id === activeId)
306
+ return true;
307
+ if (item.href && item.href === activeId)
308
+ return true;
309
+ return false;
310
+ }
311
+ function hasActiveDescendant(item, activeId) {
312
+ if (!item.children)
313
+ return false;
314
+ return item.children.some(c => isActive(c, activeId) || hasActiveDescendant(c, activeId));
315
+ }
316
+ // ── Builder ──────────────────────────────────────────────────────────────────
317
+ class Menu {
318
+ constructor(id, options = {}) {
319
+ this._el = null;
320
+ this._openSubmenus = new Set();
321
+ this._onChange = null;
322
+ this.id = id || generateId('menu');
323
+ this._opts = { width: '240px', collapsible: false, collapsed: false, ...options };
324
+ this._activeId = options.active || '';
325
+ this._collapsed = options.collapsed ?? false;
326
+ this._injectStyles();
327
+ }
328
+ // ── Fluent API ───────────────────────────────────────────────────────
329
+ items(val) { this._opts.items = val; this._rerender(); return this; }
330
+ footer(val) { this._opts.footer = val; this._rerender(); return this; }
331
+ active(val) { this._activeId = val; this._rerender(); return this; }
332
+ title(val) { this._opts.title = val; this._rerender(); return this; }
333
+ titleIcon(val) { this._opts.titleIcon = val; this._rerender(); return this; }
334
+ collapsible(val) { this._opts.collapsible = val; this._rerender(); return this; }
335
+ collapsed(val) { return val ? this.collapse() : this.expand(); }
336
+ class(val) { this._opts.class = val; return this; }
337
+ style(val) { this._opts.style = val; return this; }
338
+ target(val) { this._opts.target = val; return this; }
339
+ onNavigate(fn) { this._opts.onNavigate = fn; return this; }
340
+ onSelect(fn) { this._opts.onSelect = fn; return this; }
341
+ onChange(fn) { this._onChange = fn; return this; }
342
+ width(val) {
343
+ this._opts.width = val;
344
+ if (this._el) {
345
+ this._el.style.width = this._collapsed ? '52px' : val;
346
+ this._el.style.setProperty('--menu-width', val);
347
+ }
348
+ return this;
349
+ }
350
+ collapse() {
351
+ this._collapsed = true;
352
+ if (this._el) {
353
+ this._el.classList.add('jux-menu--collapsed');
354
+ this._el.style.width = '52px';
355
+ }
356
+ return this;
357
+ }
358
+ expand() {
359
+ this._collapsed = false;
360
+ if (this._el) {
361
+ this._el.classList.remove('jux-menu--collapsed');
362
+ this._el.style.width = this._opts.width || '240px';
363
+ }
364
+ return this;
365
+ }
366
+ toggle() { return this._collapsed ? this.expand() : this.collapse(); }
367
+ // ── get*/set* delegation ─────────────────────────────────────────────
368
+ getItems() { return this._opts.items || []; }
369
+ getFooter() { return this._opts.footer || []; }
370
+ getActive() { return this._activeId; }
371
+ getWidth() { return this._opts.width || '240px'; }
372
+ getTitle() { return this._opts.title || ''; }
373
+ getTitleIcon() { return this._opts.titleIcon || ''; }
374
+ getCollapsible() { return this._opts.collapsible || false; }
375
+ getCollapsed() { return this._collapsed; }
376
+ getCollapse() { return this._collapsed; }
377
+ getExpand() { return !this._collapsed; }
378
+ getToggle() { return this._collapsed; }
379
+ getClass() { return this._opts.class || ''; }
380
+ getStyle() { return this._opts.style || ''; }
381
+ getTarget() { return this._opts.target || ''; }
382
+ getOnNavigate() { return this._opts.onNavigate; }
383
+ getOnSelect() { return this._opts.onSelect; }
384
+ setItems(val) { return this.items(val); }
385
+ setFooter(val) { return this.footer(val); }
386
+ setActive(val) { return this.active(val); }
387
+ setWidth(val) { return this.width(val); }
388
+ setTitle(val) { return this.title(val); }
389
+ setTitleIcon(val) { return this.titleIcon(val); }
390
+ setCollapsible(val) { return this.collapsible(val); }
391
+ setCollapsed(val) { return this.collapsed(val); }
392
+ setCollapse() { return this.collapse(); }
393
+ setExpand() { return this.expand(); }
394
+ setToggle() { return this.toggle(); }
395
+ setClass(val) { return this.class(val); }
396
+ setStyle(val) { return this.style(val); }
397
+ setTarget(val) { return this.target(val); }
398
+ setOnNavigate(fn) { return this.onNavigate(fn); }
399
+ setOnSelect(fn) { return this.onSelect(fn); }
400
+ getElement() { return this._el; }
401
+ isCollapsed() { return this._collapsed; }
402
+ getActiveId() { return this._activeId; }
403
+ get state() { return pageState[this.id]; }
404
+ // ── Render ───────────────────────────────────────────────────────────
405
+ render(target) {
406
+ this._el = this._build();
407
+ let cont = null;
408
+ if (target instanceof HTMLElement) {
409
+ cont = target;
410
+ }
411
+ else if (typeof target === 'string') {
412
+ cont = document.getElementById(target) || document.querySelector(target);
413
+ }
414
+ else if (this._opts.target) {
415
+ cont = document.getElementById(this._opts.target) || document.querySelector(this._opts.target);
416
+ }
417
+ else {
418
+ cont = document.getElementById('app');
419
+ }
420
+ cont?.appendChild(this._el);
421
+ return this;
422
+ }
423
+ into(target) { return this.render(target); }
424
+ destroy() {
425
+ pageState.__unregister?.(this.id);
426
+ this._el?.remove();
427
+ this._el = null;
428
+ }
429
+ // ── Styles ───────────────────────────────────────────────────────────
430
+ _injectStyles() {
431
+ injectMenuStyles();
432
+ }
433
+ // ── Build DOM ────────────────────────────────────────────────────────
434
+ _build() {
435
+ const root = document.createElement('nav');
436
+ root.id = this.id;
437
+ root.setAttribute('role', 'navigation');
438
+ root.className = 'jux-menu'
439
+ + (this._collapsed ? ' jux-menu--collapsed' : '')
440
+ + (this._opts.class ? ' ' + this._opts.class : '');
441
+ // Set explicit width directly — CSS var alone won't win over inline styles
442
+ root.style.width = this._collapsed ? '52px' : (this._opts.width || '240px');
443
+ if (this._opts.style)
444
+ root.style.cssText += this._opts.style;
445
+ // Header
446
+ if (this._opts.title || this._opts.titleIcon || this._opts.collapsible) {
447
+ root.appendChild(this._buildHeader());
448
+ }
449
+ // Main scroll area
450
+ const scroll = document.createElement('div');
451
+ scroll.className = 'jux-menu__scroll';
452
+ if (this._opts.items?.length) {
453
+ this._renderItems(this._opts.items, scroll, 0);
454
+ }
455
+ root.appendChild(scroll);
456
+ // Footer
457
+ if (this._opts.footer?.length) {
458
+ const footer = document.createElement('div');
459
+ footer.className = 'jux-menu__footer';
460
+ this._renderItems(this._opts.footer, footer, 0);
461
+ root.appendChild(footer);
462
+ }
463
+ return root;
464
+ }
465
+ _buildHeader() {
466
+ const header = document.createElement('div');
467
+ header.className = 'jux-menu__header';
468
+ if (this._opts.titleIcon) {
469
+ const icon = document.createElement('span');
470
+ icon.className = 'jux-menu__header-icon';
471
+ icon.innerHTML = this._opts.titleIcon;
472
+ header.appendChild(icon);
473
+ }
474
+ if (this._opts.title) {
475
+ const title = document.createElement('span');
476
+ title.className = 'jux-menu__header-title';
477
+ title.textContent = this._opts.title;
478
+ header.appendChild(title);
479
+ }
480
+ if (this._opts.collapsible) {
481
+ const btn = document.createElement('button');
482
+ btn.className = 'jux-menu__collapse-btn';
483
+ btn.setAttribute('aria-label', this._collapsed ? 'Expand menu' : 'Collapse menu');
484
+ btn.innerHTML = this._collapsed ? '❯' : '❮';
485
+ btn.addEventListener('click', () => {
486
+ this.toggle();
487
+ btn.innerHTML = this._collapsed ? '❯' : '❮';
488
+ btn.setAttribute('aria-label', this._collapsed ? 'Expand menu' : 'Collapse menu');
489
+ });
490
+ header.appendChild(btn);
491
+ }
492
+ return header;
493
+ }
494
+ _renderItems(items, container, depth) {
495
+ for (const item of items) {
496
+ const kind = item.kind ?? 'item';
497
+ if (kind === 'spacer') {
498
+ const spacer = document.createElement('div');
499
+ spacer.className = 'jux-menu__spacer';
500
+ container.appendChild(spacer);
501
+ continue;
502
+ }
503
+ if (kind === 'partition') {
504
+ const p = document.createElement('div');
505
+ p.className = 'jux-menu__partition';
506
+ if (item.label) {
507
+ const lbl = document.createElement('span');
508
+ lbl.textContent = item.label;
509
+ p.appendChild(lbl);
510
+ }
511
+ container.appendChild(p);
512
+ continue;
513
+ }
514
+ if (kind === 'group') {
515
+ if (item.label) {
516
+ const grpLabel = document.createElement('div');
517
+ grpLabel.className = 'jux-menu__group-label';
518
+ grpLabel.textContent = item.label;
519
+ container.appendChild(grpLabel);
520
+ }
521
+ if (item.children?.length) {
522
+ this._renderItems(item.children, container, depth);
523
+ }
524
+ continue;
525
+ }
526
+ // Regular item (possibly with submenu)
527
+ container.appendChild(this._buildItem(item, depth));
528
+ }
529
+ }
530
+ _buildItem(item, depth) {
531
+ const itemId = item.id || item.href || item.label || generateId('mi');
532
+ const hasChildren = !!item.children?.length;
533
+ const active = isActive(item, this._activeId);
534
+ const openByDefault = hasActiveDescendant(item, this._activeId);
535
+ if (openByDefault)
536
+ this._openSubmenus.add(itemId);
537
+ const isOpen = this._openSubmenus.has(itemId);
538
+ const wrapper = document.createElement('div');
539
+ // The clickable element — anchor if href, button otherwise
540
+ const el = item.href && !hasChildren
541
+ ? document.createElement('a')
542
+ : document.createElement('button');
543
+ el.className = 'jux-menu__item'
544
+ + (active ? ' jux-menu__item--active' : '')
545
+ + (item.disabled ? ' jux-menu__item--disabled' : '')
546
+ + (hasChildren ? ' jux-menu__item--has-children' : '')
547
+ + (hasChildren && isOpen ? ' jux-menu__item--open' : '');
548
+ if (item.href && el instanceof HTMLAnchorElement) {
549
+ el.href = item.href;
550
+ }
551
+ if (item.disabled)
552
+ el.setAttribute('aria-disabled', 'true');
553
+ if (item.label)
554
+ el.setAttribute('data-label', item.label);
555
+ el.setAttribute('type', 'button');
556
+ // Icon
557
+ if (item.icon) {
558
+ const icon = document.createElement('span');
559
+ icon.className = 'jux-menu__item-icon';
560
+ icon.innerHTML = item.icon;
561
+ el.appendChild(icon);
562
+ }
563
+ // Label
564
+ const label = document.createElement('span');
565
+ label.className = 'jux-menu__item-label';
566
+ label.textContent = item.label || '';
567
+ el.appendChild(label);
568
+ // Badge
569
+ if (item.badge !== undefined) {
570
+ const badge = document.createElement('span');
571
+ badge.className = 'jux-menu__item-badge'
572
+ + (item.badgeVariant ? ` jux-menu__item-badge--${item.badgeVariant}` : '');
573
+ badge.textContent = String(item.badge);
574
+ el.appendChild(badge);
575
+ }
576
+ // Chevron for submenus
577
+ if (hasChildren) {
578
+ const chevron = document.createElement('span');
579
+ chevron.className = 'jux-menu__item-chevron';
580
+ chevron.innerHTML = '❯';
581
+ el.appendChild(chevron);
582
+ }
583
+ // Click handler
584
+ el.addEventListener('click', (e) => {
585
+ if (item.disabled) {
586
+ e.preventDefault();
587
+ return;
588
+ }
589
+ if (hasChildren) {
590
+ e.preventDefault();
591
+ if (this._openSubmenus.has(itemId)) {
592
+ this._openSubmenus.delete(itemId);
593
+ el.classList.remove('jux-menu__item--open');
594
+ sub?.classList.remove('jux-menu__submenu--open');
595
+ }
596
+ else {
597
+ this._openSubmenus.add(itemId);
598
+ el.classList.add('jux-menu__item--open');
599
+ sub?.classList.add('jux-menu__submenu--open');
600
+ }
601
+ return;
602
+ }
603
+ if (item.href) {
604
+ e.preventDefault();
605
+ this._setActive(itemId);
606
+ this._opts.onNavigate?.(item.href, item);
607
+ if (window.navigateTo)
608
+ window.navigateTo(item.href);
609
+ }
610
+ item.onClick?.(item, e);
611
+ this._opts.onSelect?.(item);
612
+ this._onChange?.(item);
613
+ // Update pageState
614
+ if (!pageState[this.id])
615
+ pageState[this.id] = {};
616
+ pageState[this.id].selected = item.id || item.href || item.label;
617
+ pageState[this.id].item = item;
618
+ });
619
+ wrapper.appendChild(el);
620
+ // Submenu
621
+ let sub;
622
+ if (hasChildren) {
623
+ sub = document.createElement('div');
624
+ sub.className = 'jux-menu__submenu' + (isOpen ? ' jux-menu__submenu--open' : '');
625
+ this._renderItems(item.children, sub, depth + 1);
626
+ wrapper.appendChild(sub);
627
+ }
628
+ return wrapper;
629
+ }
630
+ _setActive(id) {
631
+ this._activeId = id;
632
+ // Update active classes in DOM without full re-render
633
+ this._el?.querySelectorAll('.jux-menu__item').forEach(el => {
634
+ const label = el.getAttribute('data-label') || '';
635
+ const href = el.href || '';
636
+ const elId = el.getAttribute('data-id') || '';
637
+ const active = elId === id || href.endsWith(id) || label === id;
638
+ el.classList.toggle('jux-menu__item--active', active);
639
+ });
640
+ }
641
+ _rerender() {
642
+ if (!this._el)
643
+ return;
644
+ const parent = this._el.parentElement;
645
+ const next = this._el.nextSibling;
646
+ this._el.remove();
647
+ this._el = this._build();
648
+ if (parent) {
649
+ if (next)
650
+ parent.insertBefore(this._el, next);
651
+ else
652
+ parent.appendChild(this._el);
653
+ }
654
+ }
655
+ }
656
+ // ── Factory ──────────────────────────────────────────────────────────────────
657
+ export function menu(id, options = {}) {
658
+ const m = new Menu(id, options);
659
+ pageState.__register(m);
660
+ m.render();
661
+ return m;
662
+ }
663
+ export { Menu };
664
+ export default menu;
665
+ //# sourceMappingURL=menu.js.map