@waggylabs/yumekit 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/components/y-appbar.d.ts +28 -0
  2. package/dist/components/y-appbar.js +1707 -0
  3. package/dist/components/y-avatar.d.ts +5 -0
  4. package/dist/components/y-avatar.js +108 -0
  5. package/dist/components/y-badge.d.ts +15 -0
  6. package/dist/components/y-badge.js +149 -0
  7. package/dist/components/y-button.d.ts +19 -0
  8. package/dist/components/y-button.js +557 -0
  9. package/dist/components/y-card.d.ts +8 -0
  10. package/dist/components/y-card.js +157 -0
  11. package/dist/components/y-checkbox.d.ts +20 -0
  12. package/dist/components/y-checkbox.js +256 -0
  13. package/dist/components/y-dialog.d.ts +18 -0
  14. package/dist/components/y-dialog.js +232 -0
  15. package/dist/components/y-drawer.d.ts +1 -0
  16. package/dist/components/y-drawer.js +459 -0
  17. package/dist/components/y-icon.d.ts +19 -0
  18. package/dist/components/y-icon.js +270 -0
  19. package/dist/components/y-input.d.ts +15 -0
  20. package/dist/components/y-input.js +233 -0
  21. package/dist/components/y-menu.d.ts +26 -0
  22. package/dist/components/y-menu.js +322 -0
  23. package/dist/components/y-panel.d.ts +23 -0
  24. package/dist/components/y-panel.js +366 -0
  25. package/dist/components/y-panelbar.d.ts +3 -0
  26. package/dist/components/y-panelbar.js +27 -0
  27. package/dist/components/y-progress.d.ts +38 -0
  28. package/dist/components/y-progress.js +328 -0
  29. package/dist/components/y-radio.d.ts +16 -0
  30. package/dist/components/y-radio.js +202 -0
  31. package/dist/components/y-select.d.ts +33 -0
  32. package/dist/components/y-select.js +524 -0
  33. package/dist/components/y-slider.d.ts +34 -0
  34. package/dist/components/y-slider.js +387 -0
  35. package/dist/components/y-switch.d.ts +24 -0
  36. package/dist/components/y-switch.js +373 -0
  37. package/dist/components/y-table.d.ts +25 -0
  38. package/dist/components/y-table.js +327 -0
  39. package/dist/components/y-tabs.d.ts +21 -0
  40. package/dist/components/y-tabs.js +286 -0
  41. package/dist/components/y-tag.d.ts +7 -0
  42. package/dist/components/y-tag.js +218 -0
  43. package/dist/components/y-theme.d.ts +10 -0
  44. package/dist/components/y-theme.js +115 -0
  45. package/dist/components/y-toast.d.ts +33 -0
  46. package/dist/components/y-toast.js +248 -0
  47. package/dist/components/y-tooltip.d.ts +25 -0
  48. package/dist/components/y-tooltip.js +228 -0
  49. package/dist/icons/all.d.ts +1 -0
  50. package/dist/icons/all.js +208 -0
  51. package/dist/icons/index.d.ts +19 -0
  52. package/dist/icons/registry.d.ts +3 -0
  53. package/dist/icons/registry.js +30 -0
  54. package/dist/index.d.ts +22 -0
  55. package/dist/index.iife.d.ts +1 -0
  56. package/dist/index.js +7137 -0
  57. package/dist/modules/helpers.d.ts +44 -0
  58. package/dist/modules/helpers.js +124 -0
  59. package/dist/react.d.ts +214 -0
  60. package/dist/styles/blue-dark.css +138 -0
  61. package/dist/styles/blue-light.css +138 -0
  62. package/dist/styles/orange-dark.css +138 -0
  63. package/dist/styles/orange-light.css +138 -0
  64. package/dist/styles/style.css +8 -0
  65. package/dist/styles/variables.css +594 -0
  66. package/dist/yumekit.min.js +1 -0
  67. package/package.json +55 -0
@@ -0,0 +1,1707 @@
1
+ import { getIcon } from '../icons/registry.js';
2
+
3
+ class YumeButton extends HTMLElement {
4
+ static get observedAttributes() {
5
+ return [
6
+ "left-icon",
7
+ "right-icon",
8
+ "color",
9
+ "size",
10
+ "style-type",
11
+ "type",
12
+ "disabled",
13
+ "name",
14
+ "value",
15
+ "autofocus",
16
+ "form",
17
+ "formaction",
18
+ "formenctype",
19
+ "formmethod",
20
+ "formnovalidate",
21
+ "formtarget",
22
+ "aria-label",
23
+ "aria-pressed",
24
+ "aria-hidden",
25
+ ];
26
+ }
27
+
28
+ constructor() {
29
+ super();
30
+ this.attachShadow({ mode: "open" });
31
+ this.init();
32
+ }
33
+
34
+ connectedCallback() {
35
+ if (!this.hasAttribute("size")) {
36
+ this.setAttribute("size", "medium");
37
+ }
38
+
39
+ this.init();
40
+ }
41
+
42
+ attributeChangedCallback(name, oldValue, newValue) {
43
+ const attributes = YumeButton.observedAttributes;
44
+
45
+ if (oldValue !== newValue && attributes.includes(name)) {
46
+ if (newValue === null) {
47
+ this.button.removeAttribute(name);
48
+ } else {
49
+ this.button.setAttribute(name, newValue);
50
+ }
51
+ }
52
+
53
+ this.init();
54
+
55
+ if (["color", "size", "style-type", "disabled"].includes(name)) {
56
+ this.updateStyles();
57
+ }
58
+ }
59
+
60
+ get value() {
61
+ if (this.hasAttribute("multiple")) {
62
+ return Array.from(this.selectedValues).join(",");
63
+ } else {
64
+ return this.selectedValues.size
65
+ ? Array.from(this.selectedValues)[0]
66
+ : "";
67
+ }
68
+ }
69
+
70
+ set value(newVal) {
71
+ if (this.hasAttribute("multiple")) {
72
+ if (typeof newVal === "string") {
73
+ this.selectedValues = new Set(
74
+ newVal.split(",").map((s) => s.trim()),
75
+ );
76
+ } else if (Array.isArray(newVal)) {
77
+ this.selectedValues = new Set(newVal);
78
+ }
79
+ } else {
80
+ if (typeof newVal === "string") {
81
+ this.selectedValues = new Set([newVal.trim()]);
82
+ } else {
83
+ this.selectedValues = new Set();
84
+ }
85
+ }
86
+
87
+ this.setAttribute("value", newVal);
88
+ }
89
+
90
+ setOptions(options) {
91
+ this.setAttribute("options", JSON.stringify(options));
92
+ }
93
+
94
+ handleClick() {
95
+ const detail = {};
96
+ const eventType = this.getAttribute("data-event");
97
+
98
+ if (this.hasAttribute("disabled") || !eventType) return;
99
+
100
+ Array.from(this.attributes)
101
+ .filter((attr) => attr.name.startsWith("data-detail-"))
102
+ .forEach((attr) => {
103
+ const key = attr.name.replace("data-detail-", "");
104
+ detail[key] = attr.value;
105
+ });
106
+
107
+ this.dispatchEvent(
108
+ new CustomEvent(eventType, {
109
+ detail,
110
+ bubbles: true,
111
+ composed: true,
112
+ }),
113
+ );
114
+ }
115
+
116
+ init() {
117
+ this.applyStyles();
118
+ this.render();
119
+ this.proxyNativeOnClick();
120
+ this.addEventListeners();
121
+ }
122
+
123
+ proxyNativeOnClick() {
124
+ try {
125
+ Object.defineProperty(this, "onclick", {
126
+ get: () => this.button.onclick,
127
+ set: (value) => {
128
+ this.button.onclick = value;
129
+ },
130
+ configurable: true,
131
+ enumerable: true,
132
+ });
133
+ } catch (e) {
134
+ console.warn("Could not redefine onclick:", e);
135
+ }
136
+ }
137
+
138
+ updateButtonAttributes() {
139
+ const attributes = YumeButton.observedAttributes;
140
+
141
+ attributes.forEach((attr) => {
142
+ if (this.hasAttribute(attr)) {
143
+ this.button.setAttribute(attr, this.getAttribute(attr));
144
+ } else {
145
+ this.button.removeAttribute(attr);
146
+ }
147
+ });
148
+ }
149
+
150
+ manageSlotVisibility(slotName, selector) {
151
+ const slot = slotName
152
+ ? this.shadowRoot.querySelector(`slot[name="${slotName}"]`)
153
+ : this.shadowRoot.querySelector("slot:not([name])");
154
+ const container = this.shadowRoot.querySelector(selector);
155
+
156
+ const updateVisibility = () => {
157
+ const hasContent = slot
158
+ .assignedNodes({ flatten: true })
159
+ .some(
160
+ (n) =>
161
+ !(
162
+ n.nodeType === Node.TEXT_NODE &&
163
+ n.textContent.trim() === ""
164
+ ),
165
+ );
166
+ container.style.display = hasContent ? "inline-flex" : "none";
167
+ };
168
+
169
+ updateVisibility();
170
+ slot.addEventListener("slotchange", updateVisibility);
171
+ }
172
+
173
+ render() {
174
+ if (!this.button) {
175
+ this.button = document.createElement("button");
176
+ this.button.classList.add("button");
177
+ this.button.setAttribute("role", "button");
178
+ this.button.setAttribute("tabindex", "0");
179
+ this.button.setAttribute("part", "button");
180
+ this.shadowRoot.appendChild(this.button);
181
+ }
182
+
183
+ this.updateButtonAttributes();
184
+
185
+ if (this.hasAttribute("disabled")) {
186
+ this.button.setAttribute("disabled", "");
187
+ this.button.setAttribute("aria-disabled", "true");
188
+ } else {
189
+ this.button.removeAttribute("disabled");
190
+ this.button.setAttribute("aria-disabled", "false");
191
+ }
192
+
193
+ this.button.innerHTML = `
194
+ <span class="icon left-icon" part="left-icon"><slot name="left-icon"></slot></span>
195
+ <span class="label" part="label"><slot></slot></span>
196
+ <span class="icon right-icon" part="right-icon"><slot name="right-icon"></slot></span>
197
+ `;
198
+
199
+ this.manageSlotVisibility("left-icon", ".left-icon");
200
+ this.manageSlotVisibility("right-icon", ".right-icon");
201
+ this.manageSlotVisibility("", ".label");
202
+ }
203
+
204
+ applyStyles() {
205
+ const style = document.createElement("style");
206
+ style.textContent = `
207
+ :host {
208
+ display: inline-block;
209
+ }
210
+
211
+ @font-face {
212
+ font-family: "Lexend";
213
+ font-display: swap;
214
+ }
215
+
216
+ .button {
217
+ box-sizing: border-box;
218
+ display: inline-flex;
219
+ min-height: var(--button-min-height, var(--sizing-medium, 40px));
220
+ min-width: var(--button-min-width, var(--sizing-medium, 40px));
221
+ padding: var(--button-padding, var(--component-button-padding-medium));
222
+ gap: var(--button-gap, var(--component-button-padding-medium));
223
+ justify-content: center;
224
+ align-items: center;
225
+ position: relative;
226
+ overflow: hidden;
227
+ border-radius: var(--component-button-border-radius-outer, 4px);
228
+ border: var(--component-button-border-width, 1px) solid var(--border-color, var(--base-content--, #f7f7fa));
229
+ background: var(--background-color, #0c0c0d);
230
+ transition: background-color 0.1s, color 0.1s, border-color 0.1s;
231
+ cursor: pointer;
232
+ color: var(--text-color);
233
+ font-family: var(--font-family-body, Lexend, sans-serif);
234
+ font-size: var(--font-size-button, 1em);
235
+ line-height: 1;
236
+ }
237
+
238
+ .button:disabled {
239
+ opacity: 0.5;
240
+ cursor: not-allowed;
241
+ }
242
+
243
+ .button:hover:not(:disabled),
244
+ .button:hover:not(:disabled) .button-content {
245
+ background: var(--hover-background-color);
246
+ color: var(--hover-text-color);
247
+ border-color: var(--hover-border-color);
248
+ }
249
+ .button:focus:not(:disabled),
250
+ .button:focus:not(:disabled) .button-content {
251
+ background: var(--focus-background-color);
252
+ color: var(--focus-text-color);
253
+ border-color: var(--focus-border-color);
254
+ }
255
+ .button:active:not(:disabled),
256
+ .button:active:not(:disabled) .button-content {
257
+ background: var(--active-background-color);
258
+ color: var(--active-text-color);
259
+ border-color: var(--active-border-color);
260
+ }
261
+ .icon {
262
+ display: flex;
263
+ min-width: 16px;
264
+ min-height: 1em;
265
+ justify-content: center;
266
+ align-items: center;
267
+ }
268
+ .label {
269
+ line-height: inherit;
270
+ min-height: 1em;
271
+ align-items: center;
272
+ }
273
+ `;
274
+ this.shadowRoot.appendChild(style);
275
+ }
276
+
277
+ addEventListeners() {
278
+ this.button.addEventListener("focus", () => {
279
+ this.dispatchEvent(
280
+ new CustomEvent("focus", { bubbles: true, composed: true }),
281
+ );
282
+ });
283
+
284
+ this.button.addEventListener("blur", () => {
285
+ this.dispatchEvent(
286
+ new CustomEvent("blur", { bubbles: true, composed: true }),
287
+ );
288
+ });
289
+
290
+ this.button.addEventListener("keydown", (event) => {
291
+ this.dispatchEvent(
292
+ new CustomEvent("keydown", {
293
+ detail: { key: event.key, code: event.code },
294
+ bubbles: true,
295
+ composed: true,
296
+ }),
297
+ );
298
+ });
299
+
300
+ this.button.addEventListener("keyup", (event) => {
301
+ this.dispatchEvent(
302
+ new CustomEvent("keyup", {
303
+ detail: { key: event.key, code: event.code },
304
+ bubbles: true,
305
+ composed: true,
306
+ }),
307
+ );
308
+ });
309
+
310
+ this.button.addEventListener("click", (event) => {
311
+ this.handleClick();
312
+
313
+ if (this.getAttribute("type") === "submit") {
314
+ const form = this.closest("form");
315
+ if (form) {
316
+ event.preventDefault();
317
+ form.requestSubmit();
318
+ }
319
+ }
320
+ });
321
+ }
322
+
323
+ updateStyles() {
324
+ const color = this.getAttribute("color") || "base";
325
+ const size = this.getAttribute("size") || "medium";
326
+ const styleType = this.getAttribute("style-type") || "outlined";
327
+
328
+ const colorVars = {
329
+ primary: [
330
+ "--primary-content--",
331
+ "--primary-content-hover",
332
+ "--primary-content-active",
333
+ "--primary-background-component",
334
+ "--primary-background-hover",
335
+ "--primary-background-active",
336
+ "--primary-content-inverse",
337
+ ],
338
+ secondary: [
339
+ "--secondary-content--",
340
+ "--secondary-content-hover",
341
+ "--secondary-content-active",
342
+ "--secondary-background-component",
343
+ "--secondary-background-hover",
344
+ "--secondary-background-active",
345
+ "--secondary-content-inverse",
346
+ ],
347
+ base: [
348
+ "--base-content--",
349
+ "--base-content-lighter",
350
+ "--base-content-lightest",
351
+ "--base-background-component",
352
+ "--base-background-hover",
353
+ "--base-background-active",
354
+ "--base-content-inverse",
355
+ ],
356
+ success: [
357
+ "--success-content--",
358
+ "--success-content-hover",
359
+ "--success-content-active",
360
+ "--success-background-component",
361
+ "--success-background-hover",
362
+ "--success-background-active",
363
+ "--success-content-inverse",
364
+ ],
365
+ error: [
366
+ "--error-content--",
367
+ "--error-content-hover",
368
+ "--error-content-active",
369
+ "--error-background-component",
370
+ "--error-background-hover",
371
+ "--error-background-active",
372
+ "--error-content-inverse",
373
+ ],
374
+ warning: [
375
+ "--warning-content--",
376
+ "--warning-content-hover",
377
+ "--warning-content-active",
378
+ "--warning-background-component",
379
+ "--warning-background-hover",
380
+ "--warning-background-active",
381
+ "--warning-content-inverse",
382
+ ],
383
+ help: [
384
+ "--help-content--",
385
+ "--help-content-hover",
386
+ "--help-content-active",
387
+ "--help-background-component",
388
+ "--help-background-hover",
389
+ "--help-background-active",
390
+ "--help-content-inverse",
391
+ ],
392
+ };
393
+
394
+ const sizeVars = {
395
+ small: [
396
+ "--component-button-padding-small",
397
+ "--component-button-padding-small",
398
+ ],
399
+ medium: [
400
+ "--component-button-padding-medium",
401
+ "--component-button-padding-medium",
402
+ ],
403
+ large: [
404
+ "--component-button-padding-large",
405
+ "--component-button-padding-large",
406
+ ],
407
+ };
408
+
409
+ const styleVars = {
410
+ outlined: {
411
+ "--background-color": `var(${colorVars[color][3]}, #0c0c0d)`,
412
+ "--border-color": `var(${colorVars[color][0]}, #f7f7fa)`,
413
+ "--text-color": `var(${colorVars[color][0]}, #f7f7fa)`,
414
+ },
415
+ filled: {
416
+ "--background-color": `var(${colorVars[color][0]}, #f7f7fa)`,
417
+ "--border-color": `var(${colorVars[color][0]}, #f7f7fa)`,
418
+ "--text-color": `var(${colorVars[color][6]}, #0c0c0d)`,
419
+ },
420
+ flat: {
421
+ "--background-color": `var(${colorVars[color][3]},#0c0c0d)`,
422
+ "--border-color": `var(${colorVars[color][3]},#0c0c0d)`,
423
+ "--text-color": `var(${colorVars[color][0]},#f7f7fa)`,
424
+ },
425
+ };
426
+
427
+ const currentStyle = styleVars[styleType] || styleVars.outlined;
428
+ Object.entries(currentStyle).forEach(([key, value]) => {
429
+ this.button.style.setProperty(key, value);
430
+ });
431
+
432
+ if (styleType === "filled") {
433
+ this.button.style.setProperty(
434
+ "--hover-background-color",
435
+ `var(${colorVars[color][1]}, #292a2b)`,
436
+ );
437
+ this.button.style.setProperty(
438
+ "--hover-text-color",
439
+ `var(${colorVars[color][6]}, #0c0c0d)`,
440
+ );
441
+ this.button.style.setProperty(
442
+ "--hover-border-color",
443
+ `var(${colorVars[color][1]}, #292a2b)`,
444
+ );
445
+ this.button.style.setProperty(
446
+ "--focus-background-color",
447
+ `var(${colorVars[color][2]}, #46474a)`,
448
+ );
449
+ this.button.style.setProperty(
450
+ "--focus-text-color",
451
+ `var(${colorVars[color][6]}, #0c0c0d)`,
452
+ );
453
+ this.button.style.setProperty(
454
+ "--focus-border-color",
455
+ `var(${colorVars[color][2]}, #46474a)`,
456
+ );
457
+ this.button.style.setProperty(
458
+ "--active-background-color",
459
+ `var(${colorVars[color][0]}, #f7f7fa)`,
460
+ );
461
+ this.button.style.setProperty(
462
+ "--active-text-color",
463
+ `var(${colorVars[color][6]}, #0c0c0d)`,
464
+ );
465
+ this.button.style.setProperty(
466
+ "--active-border-color",
467
+ `var(${colorVars[color][0]}, #f7f7fa)`,
468
+ );
469
+ } else {
470
+ const borderColor = `var(${colorVars[color][0]}, #f7f7fa)`;
471
+
472
+ this.button.style.setProperty(
473
+ "--hover-background-color",
474
+ `var(${colorVars[color][4]}, #292a2b)`,
475
+ );
476
+ this.button.style.setProperty(
477
+ "--hover-text-color",
478
+ `var(${colorVars[color][0]}, #f7f7fa)`,
479
+ );
480
+ this.button.style.setProperty(
481
+ "--focus-background-color",
482
+ `var(${colorVars[color][5]}, #46474a)`,
483
+ );
484
+ this.button.style.setProperty(
485
+ "--focus-text-color",
486
+ `var(${colorVars[color][0]}, #f7f7fa)`,
487
+ );
488
+ this.button.style.setProperty(
489
+ "--active-background-color",
490
+ `var(${colorVars[color][0]}, #f7f7fa)`,
491
+ );
492
+ this.button.style.setProperty(
493
+ "--active-text-color",
494
+ `var(${colorVars[color][6]}, #0c0c0d)`,
495
+ );
496
+
497
+ if (styleType === "outlined") {
498
+ // Outlined buttons keep their border color across all states
499
+ this.button.style.setProperty(
500
+ "--hover-border-color",
501
+ borderColor,
502
+ );
503
+ this.button.style.setProperty(
504
+ "--focus-border-color",
505
+ borderColor,
506
+ );
507
+ this.button.style.setProperty(
508
+ "--active-border-color",
509
+ borderColor,
510
+ );
511
+ } else {
512
+ // Flat buttons match border to background
513
+ this.button.style.setProperty(
514
+ "--hover-border-color",
515
+ `var(${colorVars[color][4]}, #292a2b)`,
516
+ );
517
+ this.button.style.setProperty(
518
+ "--focus-border-color",
519
+ `var(${colorVars[color][5]}, #46474a)`,
520
+ );
521
+ this.button.style.setProperty(
522
+ "--active-border-color",
523
+ `var(${colorVars[color][0]}, #f7f7fa)`,
524
+ );
525
+ }
526
+ }
527
+
528
+ const [contentPadding, buttonPadding] =
529
+ sizeVars[size] || sizeVars.medium;
530
+ this.button.style.setProperty(
531
+ "--button-padding",
532
+ `var(${buttonPadding}, var(--component-button-padding-medium))`,
533
+ );
534
+ this.button.style.setProperty(
535
+ "--button-gap",
536
+ `var(${contentPadding}, var(--component-button-padding-medium))`,
537
+ );
538
+
539
+ const minSizeMapping = {
540
+ small: "var(--sizing-small, 32px)",
541
+ medium: "var(--sizing-medium, 40px)",
542
+ large: "var(--sizing-large, 56px)",
543
+ };
544
+ this.button.style.setProperty(
545
+ "--button-min-height",
546
+ minSizeMapping[size] || "40px",
547
+ );
548
+ this.button.style.setProperty(
549
+ "--button-min-width",
550
+ minSizeMapping[size] || "40px",
551
+ );
552
+ }
553
+ }
554
+
555
+ if (!customElements.get("y-button")) {
556
+ customElements.define("y-button", YumeButton);
557
+ }
558
+
559
+ // Allowlist-based SVG sanitizer — only known-safe elements and attributes are kept.
560
+ const ALLOWED_ELEMENTS = new Set([
561
+ "svg",
562
+ "g",
563
+ "path",
564
+ "circle",
565
+ "ellipse",
566
+ "rect",
567
+ "line",
568
+ "polyline",
569
+ "polygon",
570
+ "text",
571
+ "tspan",
572
+ "defs",
573
+ "clippath",
574
+ "mask",
575
+ "lineargradient",
576
+ "radialgradient",
577
+ "stop",
578
+ "symbol",
579
+ "title",
580
+ "desc",
581
+ "metadata",
582
+ ]);
583
+
584
+ const ALLOWED_ATTRS = new Set([
585
+ "viewbox",
586
+ "xmlns",
587
+ "fill",
588
+ "stroke",
589
+ "stroke-width",
590
+ "stroke-linecap",
591
+ "stroke-linejoin",
592
+ "stroke-dasharray",
593
+ "stroke-dashoffset",
594
+ "stroke-miterlimit",
595
+ "stroke-opacity",
596
+ "fill-opacity",
597
+ "fill-rule",
598
+ "clip-rule",
599
+ "opacity",
600
+ "d",
601
+ "cx",
602
+ "cy",
603
+ "r",
604
+ "rx",
605
+ "ry",
606
+ "x",
607
+ "x1",
608
+ "x2",
609
+ "y",
610
+ "y1",
611
+ "y2",
612
+ "width",
613
+ "height",
614
+ "points",
615
+ "transform",
616
+ "id",
617
+ "class",
618
+ "clip-path",
619
+ "mask",
620
+ "offset",
621
+ "stop-color",
622
+ "stop-opacity",
623
+ "gradient-units",
624
+ "gradienttransform",
625
+ "gradientunits",
626
+ "spreadmethod",
627
+ "patternunits",
628
+ "patterntransform",
629
+ "font-size",
630
+ "font-family",
631
+ "font-weight",
632
+ "text-anchor",
633
+ "dominant-baseline",
634
+ "alignment-baseline",
635
+ "dx",
636
+ "dy",
637
+ "rotate",
638
+ "textlength",
639
+ "lengthadjust",
640
+ "display",
641
+ "visibility",
642
+ "color",
643
+ "vector-effect",
644
+ ]);
645
+
646
+ function sanitizeSvg(raw) {
647
+ if (!raw) return "";
648
+ const doc = new DOMParser().parseFromString(raw, "image/svg+xml");
649
+ const svg = doc.querySelector("svg");
650
+ if (!svg) return "";
651
+
652
+ const walk = (el) => {
653
+ for (const child of [...el.children]) {
654
+ if (!ALLOWED_ELEMENTS.has(child.tagName.toLowerCase())) {
655
+ child.remove();
656
+ continue;
657
+ }
658
+ for (const attr of [...child.attributes]) {
659
+ if (!ALLOWED_ATTRS.has(attr.name.toLowerCase())) {
660
+ child.removeAttribute(attr.name);
661
+ }
662
+ }
663
+ walk(child);
664
+ }
665
+ };
666
+
667
+ // Sanitize the <svg> element's own attributes
668
+ for (const attr of [...svg.attributes]) {
669
+ if (!ALLOWED_ATTRS.has(attr.name.toLowerCase())) {
670
+ svg.removeAttribute(attr.name);
671
+ }
672
+ }
673
+ walk(svg);
674
+ return svg.outerHTML;
675
+ }
676
+
677
+ // Cache sanitized SVG markup per icon name to avoid repeated DOMParser + DOM-walk
678
+ // on every render. The cache is naturally bounded by the number of registered icons.
679
+ const sanitizedSvgCache = new Map();
680
+
681
+ function getCachedSvg(name) {
682
+ if (sanitizedSvgCache.has(name)) {
683
+ return sanitizedSvgCache.get(name);
684
+ }
685
+ const result = sanitizeSvg(getIcon(name));
686
+ sanitizedSvgCache.set(name, result);
687
+ return result;
688
+ }
689
+
690
+ class YumeIcon extends HTMLElement {
691
+ static get observedAttributes() {
692
+ return ["name", "size", "color", "label", "weight"];
693
+ }
694
+
695
+ constructor() {
696
+ super();
697
+ this.attachShadow({ mode: "open" });
698
+ }
699
+
700
+ connectedCallback() {
701
+ this.render();
702
+ }
703
+
704
+ attributeChangedCallback(name, oldVal, newVal) {
705
+ if (oldVal === newVal) return;
706
+ this.render();
707
+ }
708
+
709
+ get name() {
710
+ return this.getAttribute("name") || "";
711
+ }
712
+ set name(val) {
713
+ this.setAttribute("name", val);
714
+ }
715
+
716
+ get size() {
717
+ return this.getAttribute("size") || "medium";
718
+ }
719
+ set size(val) {
720
+ this.setAttribute("size", val);
721
+ }
722
+
723
+ get color() {
724
+ return this.getAttribute("color") || "";
725
+ }
726
+ set color(val) {
727
+ if (val) this.setAttribute("color", val);
728
+ else this.removeAttribute("color");
729
+ }
730
+
731
+ get label() {
732
+ return this.getAttribute("label") || "";
733
+ }
734
+ set label(val) {
735
+ if (val) this.setAttribute("label", val);
736
+ else this.removeAttribute("label");
737
+ }
738
+
739
+ get weight() {
740
+ return this.getAttribute("weight") || "";
741
+ }
742
+ set weight(val) {
743
+ if (val) this.setAttribute("weight", val);
744
+ else this.removeAttribute("weight");
745
+ }
746
+
747
+ _getColor(color) {
748
+ const map = {
749
+ base: "var(--base-content--, #f7f7fa)",
750
+ primary: "var(--primary-content--, #0576ff)",
751
+ secondary: "var(--secondary-content--, #04b8b8)",
752
+ success: "var(--success-content--, #2dba73)",
753
+ warning: "var(--warning-content--, #d17f04)",
754
+ error: "var(--error-content--, #b80421)",
755
+ help: "var(--help-content--, #5405ff)",
756
+ };
757
+ return map[color] || map.base;
758
+ }
759
+
760
+ _getSize(size) {
761
+ const map = {
762
+ small: "var(--component-icon-size-small, 16px)",
763
+ medium: "var(--component-icon-size-medium, 24px)",
764
+ large: "var(--component-icon-size-large, 32px)",
765
+ };
766
+ return map[size] || map.medium;
767
+ }
768
+
769
+ _getWeight(weight) {
770
+ const map = {
771
+ thin: "1",
772
+ regular: "1.5",
773
+ thick: "2",
774
+ };
775
+ return map[weight] || "";
776
+ }
777
+
778
+ render() {
779
+ const svg = getCachedSvg(this.name);
780
+ const sizeVal = this._getSize(this.size);
781
+ const colorVal = this.color ? this._getColor(this.color) : "inherit";
782
+ const weightVal = this._getWeight(this.weight);
783
+ const label = this.label;
784
+
785
+ if (label) {
786
+ this.setAttribute("role", "img");
787
+ this.setAttribute("aria-label", label);
788
+ this.removeAttribute("aria-hidden");
789
+ } else {
790
+ this.setAttribute("aria-hidden", "true");
791
+ this.removeAttribute("role");
792
+ this.removeAttribute("aria-label");
793
+ }
794
+
795
+ const weightCSS = weightVal
796
+ ? `.icon-wrapper svg,
797
+ .icon-wrapper svg * { stroke-width: ${weightVal} !important; }`
798
+ : "";
799
+
800
+ this.shadowRoot.innerHTML = `
801
+ <style>
802
+ :host {
803
+ display: inline-flex;
804
+ align-items: center;
805
+ justify-content: center;
806
+ width: ${sizeVal};
807
+ height: ${sizeVal};
808
+ color: ${colorVal};
809
+ line-height: 0;
810
+ }
811
+ .icon-wrapper svg {
812
+ width: 100%;
813
+ height: 100%;
814
+ }
815
+ ${weightCSS}
816
+ </style>
817
+ <span class="icon-wrapper" part="icon">${svg}</span>
818
+ `;
819
+ }
820
+ }
821
+
822
+ if (!customElements.get("y-icon")) {
823
+ customElements.define("y-icon", YumeIcon);
824
+ }
825
+
826
+ class YumeMenu extends HTMLElement {
827
+ static get observedAttributes() {
828
+ return ["items", "anchor", "visible", "direction", "size"];
829
+ }
830
+
831
+ constructor() {
832
+ super();
833
+ this.attachShadow({ mode: "open" });
834
+ this._onAnchorClick = this._onAnchorClick.bind(this);
835
+ this._onDocumentClick = this._onDocumentClick.bind(this);
836
+ this._onScrollOrResize = this._onScrollOrResize.bind(this);
837
+ }
838
+
839
+ connectedCallback() {
840
+ if (!this.hasAttribute("items")) this.items = [];
841
+ this._setupAnchor();
842
+ this.render();
843
+ document.addEventListener("click", this._onDocumentClick);
844
+ window.addEventListener("scroll", this._onScrollOrResize, true);
845
+ window.addEventListener("resize", this._onScrollOrResize);
846
+ this.style.position = "fixed";
847
+ this.style.zIndex = "1000";
848
+ this.style.display = "none";
849
+ }
850
+
851
+ disconnectedCallback() {
852
+ this._teardownAnchor();
853
+ document.removeEventListener("click", this._onDocumentClick);
854
+ window.removeEventListener("scroll", this._onScrollOrResize, true);
855
+ window.removeEventListener("resize", this._onScrollOrResize);
856
+ }
857
+
858
+ attributeChangedCallback(name, oldVal, newVal) {
859
+ if (oldVal === newVal) return;
860
+ if (name === "items" || name === "size") this.render();
861
+ if (name === "anchor") {
862
+ this._teardownAnchor();
863
+ this._setupAnchor();
864
+ }
865
+ if (name === "visible") {
866
+ this._updatePosition();
867
+ }
868
+ if (name === "direction") {
869
+ this._updatePosition();
870
+ }
871
+ }
872
+
873
+ get items() {
874
+ try {
875
+ return JSON.parse(this.getAttribute("items")) || [];
876
+ } catch {
877
+ return [];
878
+ }
879
+ }
880
+ set items(val) {
881
+ this.setAttribute("items", JSON.stringify(val));
882
+ }
883
+
884
+ get anchor() {
885
+ return this.getAttribute("anchor");
886
+ }
887
+ set anchor(val) {
888
+ this.setAttribute("anchor", val);
889
+ }
890
+
891
+ get visible() {
892
+ return this.hasAttribute("visible");
893
+ }
894
+ set visible(val) {
895
+ if (val) this.setAttribute("visible", "");
896
+ else this.removeAttribute("visible");
897
+ }
898
+
899
+ get direction() {
900
+ return this.getAttribute("direction") || "down";
901
+ }
902
+ set direction(val) {
903
+ this.setAttribute("direction", val);
904
+ }
905
+
906
+ get size() {
907
+ const sz = this.getAttribute("size");
908
+ return ["small", "medium", "large"].includes(sz) ? sz : "medium";
909
+ }
910
+ set size(val) {
911
+ if (["small", "medium", "large"].includes(val))
912
+ this.setAttribute("size", val);
913
+ else this.setAttribute("size", "medium");
914
+ }
915
+
916
+ _createMenuList(items) {
917
+ const ul = document.createElement("ul");
918
+
919
+ items.forEach((item) => {
920
+ const li = document.createElement("li");
921
+ li.className = "menuitem";
922
+ li.setAttribute("role", "menuitem");
923
+ li.setAttribute("part", "menuitem");
924
+ li.tabIndex = 0;
925
+
926
+ const contentWrapper = document.createElement("span");
927
+ contentWrapper.className = "item-content";
928
+
929
+ if (item["icon-template"]) {
930
+ const iconTpl = this._findTemplate(item["icon-template"]);
931
+ if (iconTpl)
932
+ contentWrapper.appendChild(iconTpl.content.cloneNode(true));
933
+ }
934
+
935
+ if (item.template) {
936
+ const textTpl = this._findTemplate(item.template);
937
+ if (textTpl) {
938
+ contentWrapper.appendChild(textTpl.content.cloneNode(true));
939
+ } else {
940
+ contentWrapper.textContent = item.text;
941
+ }
942
+ } else {
943
+ contentWrapper.textContent = item.text;
944
+ }
945
+
946
+ li.appendChild(contentWrapper);
947
+
948
+ if (item.url) {
949
+ li.addEventListener("click", () => {
950
+ window.location.href = item.url;
951
+ });
952
+ }
953
+
954
+ if (item.children?.length) {
955
+ const indicator = document.createElement("span");
956
+ indicator.className = "submenu-indicator";
957
+ indicator.textContent = "▶";
958
+ li.appendChild(indicator);
959
+
960
+ const submenu = this._createMenuList(item.children);
961
+ submenu.classList.add("submenu");
962
+ submenu.setAttribute("role", "menu");
963
+ li.appendChild(submenu);
964
+ }
965
+
966
+ ul.appendChild(li);
967
+ });
968
+
969
+ return ul;
970
+ }
971
+
972
+ _findTemplate(name) {
973
+ return this.querySelector(`template[slot="${name}"]`);
974
+ }
975
+
976
+ _onAnchorClick(e) {
977
+ e.stopPropagation();
978
+ this.visible = !this.visible;
979
+ }
980
+
981
+ _onDocumentClick(e) {
982
+ const path = e.composedPath();
983
+ if (this._anchorEl && path.includes(this._anchorEl)) return;
984
+ if (path.includes(this)) return;
985
+ this.visible = false;
986
+ }
987
+
988
+ _onScrollOrResize() {
989
+ if (this.visible) this._updatePosition();
990
+ }
991
+
992
+ _setupAnchor() {
993
+ const id = this.anchor;
994
+ if (id) {
995
+ const root = this.getRootNode();
996
+ const el = root?.getElementById
997
+ ? root.getElementById(id)
998
+ : document.getElementById(id);
999
+ if (el) {
1000
+ this._anchorEl = el;
1001
+ this._anchorEl.addEventListener("click", this._onAnchorClick);
1002
+ }
1003
+ }
1004
+ }
1005
+
1006
+ _teardownAnchor() {
1007
+ if (this._anchorEl) {
1008
+ this._anchorEl.removeEventListener("click", this._onAnchorClick);
1009
+ this._anchorEl = null;
1010
+ }
1011
+ }
1012
+
1013
+ _updatePosition() {
1014
+ if (!this.visible || !this._anchorEl) {
1015
+ this.style.display = "none";
1016
+ return;
1017
+ }
1018
+
1019
+ const anchorRect = this._anchorEl.getBoundingClientRect();
1020
+ const menuRect = this.getBoundingClientRect();
1021
+ const vw = window.innerWidth;
1022
+ const vh = window.innerHeight;
1023
+
1024
+ let top, left;
1025
+
1026
+ if (this.direction === "right") {
1027
+ top = anchorRect.top;
1028
+ left = anchorRect.right;
1029
+
1030
+ if (left + menuRect.width > vw) {
1031
+ left = anchorRect.left - menuRect.width;
1032
+ }
1033
+ if (top + menuRect.height > vh) {
1034
+ top = anchorRect.top - menuRect.height;
1035
+ }
1036
+ } else if (this.direction === "up") {
1037
+ top = anchorRect.top - menuRect.height;
1038
+ left = anchorRect.left;
1039
+
1040
+ if (top < 0) {
1041
+ top = anchorRect.bottom;
1042
+ }
1043
+ if (left + menuRect.width > vw) {
1044
+ left = vw - menuRect.width - 10;
1045
+ }
1046
+ } else if (this.direction === "left") {
1047
+ top = anchorRect.top;
1048
+ left = anchorRect.left - menuRect.width;
1049
+
1050
+ if (left < 0) {
1051
+ left = anchorRect.right;
1052
+ }
1053
+ if (top + menuRect.height > vh) {
1054
+ top = anchorRect.top - menuRect.height;
1055
+ }
1056
+ } else {
1057
+ // "down" (default)
1058
+ top = anchorRect.bottom;
1059
+ left = anchorRect.left;
1060
+
1061
+ if (top + menuRect.height > vh) {
1062
+ top = anchorRect.top - menuRect.height;
1063
+ }
1064
+ if (left + menuRect.width > vw) {
1065
+ left = vw - menuRect.width - 10;
1066
+ }
1067
+ }
1068
+
1069
+ top = Math.max(0, Math.min(top, vh - menuRect.height));
1070
+ left = Math.max(0, Math.min(left, vw - menuRect.width));
1071
+
1072
+ this.style.top = `${top}px`;
1073
+ this.style.left = `${left}px`;
1074
+ this.style.display = "block";
1075
+ }
1076
+
1077
+ render() {
1078
+ this.shadowRoot.innerHTML = "";
1079
+
1080
+ const paddingVar = `var(--component-button-padding-${this.size}, 0.5rem)`;
1081
+
1082
+ const style = document.createElement("style");
1083
+ style.textContent = `
1084
+ ul.menu,
1085
+ ul.submenu {
1086
+ list-style: none;
1087
+ margin: 0;
1088
+ padding: 0;
1089
+ background: var(--component-menu-background, #0c0c0d);
1090
+ border: var(--component-menu-border-width, 1px) solid var(--component-menu-border-color, #37383a);
1091
+ border-radius: var(--component-menu-border-radius, 4px);
1092
+ box-shadow: var(--component-menu-shadow, 0 2px 8px rgba(0, 0, 0, 0.15));
1093
+ min-width: 150px;
1094
+ max-height: 300px;
1095
+ overflow-y: auto;
1096
+ }
1097
+
1098
+ li.menuitem {
1099
+ cursor: pointer;
1100
+ padding: ${paddingVar};
1101
+ display: flex;
1102
+ align-items: center;
1103
+ justify-content: space-between;
1104
+ white-space: nowrap;
1105
+ color: var(--component-menu-color, #f7f7fa);
1106
+ font-size: var(--font-size-button, 1em);
1107
+ }
1108
+
1109
+ li.menuitem:hover {
1110
+ background: var(--component-menu-hover-background, #292a2b);
1111
+ }
1112
+
1113
+ ul.submenu {
1114
+ position: absolute;
1115
+ top: 0;
1116
+ left: 100%;
1117
+ display: none;
1118
+ z-index: var(--component-menu-z-index, 1001);
1119
+ }
1120
+
1121
+ li.menuitem:hover > ul.submenu {
1122
+ display: block;
1123
+ }
1124
+
1125
+ .submenu-indicator {
1126
+ font-size: 0.75em;
1127
+ margin-left: 0.5rem;
1128
+ opacity: 0.6;
1129
+ }
1130
+
1131
+ .item-content {
1132
+ flex: 1;
1133
+ }
1134
+ `;
1135
+ this.shadowRoot.appendChild(style);
1136
+
1137
+ const rootUl = this._createMenuList(this.items);
1138
+ rootUl.classList.add("menu");
1139
+ rootUl.setAttribute("role", "menu");
1140
+ rootUl.setAttribute("part", "menu");
1141
+ this.shadowRoot.appendChild(rootUl);
1142
+ }
1143
+ }
1144
+
1145
+ if (!customElements.get("y-menu")) {
1146
+ customElements.define("y-menu", YumeMenu);
1147
+ }
1148
+
1149
+ /* ================================================================== */
1150
+ /* Centralized SVG icon strings for the YumeKit component library. */
1151
+ /* */
1152
+ /* Each static icon also lives in its own .svg file in this directory */
1153
+ /* so it can be used standalone (e.g. <img src="…">, CSS background, */
1154
+ /* design tools, etc.). The strings below mirror those files — keep */
1155
+ /* them in sync when editing an icon. */
1156
+ /* ================================================================== */
1157
+
1158
+ /* ── Chevrons ─────────────────────────────────────────────────────── */
1159
+
1160
+ const chevronRight = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>`;
1161
+
1162
+ const chevronDown = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`;
1163
+
1164
+ /* ── Double chevrons (collapse / expand) ──────────────────────────── */
1165
+
1166
+ const collapseLeft = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>`;
1167
+
1168
+ const expandRight = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>`;
1169
+
1170
+ class YumeAppbar extends HTMLElement {
1171
+ static get observedAttributes() {
1172
+ return [
1173
+ "orientation",
1174
+ "collapsed",
1175
+ "items",
1176
+ "size",
1177
+ "menu-direction",
1178
+ "sticky",
1179
+ ];
1180
+ }
1181
+
1182
+ constructor() {
1183
+ super();
1184
+ this.attachShadow({ mode: "open" });
1185
+ this._onCollapseClick = this._onCollapseClick.bind(this);
1186
+ this._idCounter = 0;
1187
+ }
1188
+
1189
+ connectedCallback() {
1190
+ this.render();
1191
+ }
1192
+
1193
+ disconnectedCallback() {}
1194
+
1195
+ attributeChangedCallback(name, oldVal, newVal) {
1196
+ if (oldVal === newVal) return;
1197
+ this.render();
1198
+ }
1199
+
1200
+ get orientation() {
1201
+ return this.getAttribute("orientation") || "vertical";
1202
+ }
1203
+ set orientation(val) {
1204
+ this.setAttribute("orientation", val);
1205
+ }
1206
+
1207
+ get collapsed() {
1208
+ return this.hasAttribute("collapsed");
1209
+ }
1210
+ set collapsed(val) {
1211
+ if (val) this.setAttribute("collapsed", "");
1212
+ else this.removeAttribute("collapsed");
1213
+ }
1214
+
1215
+ get items() {
1216
+ try {
1217
+ return JSON.parse(this.getAttribute("items")) || [];
1218
+ } catch {
1219
+ return [];
1220
+ }
1221
+ }
1222
+ set items(val) {
1223
+ this.setAttribute("items", JSON.stringify(val));
1224
+ }
1225
+
1226
+ get size() {
1227
+ return this.getAttribute("size") || "medium";
1228
+ }
1229
+ set size(val) {
1230
+ this.setAttribute("size", val);
1231
+ }
1232
+
1233
+ /**
1234
+ * Direction menus pop out from nav buttons:
1235
+ * "right", "down", or unset (auto: vertical → right, horizontal → down).
1236
+ */
1237
+ get menuDirection() {
1238
+ return this.getAttribute("menu-direction") || "";
1239
+ }
1240
+ set menuDirection(val) {
1241
+ if (val) this.setAttribute("menu-direction", val);
1242
+ else this.removeAttribute("menu-direction");
1243
+ }
1244
+
1245
+ get sticky() {
1246
+ const val = this.getAttribute("sticky");
1247
+ return ["start", "end"].includes(val) ? val : false;
1248
+ }
1249
+ set sticky(val) {
1250
+ if (val === "start" || val === "end") this.setAttribute("sticky", val);
1251
+ else this.removeAttribute("sticky");
1252
+ }
1253
+
1254
+ toggle() {
1255
+ this.collapsed = !this.collapsed;
1256
+ }
1257
+
1258
+ _onCollapseClick() {
1259
+ this.toggle();
1260
+ }
1261
+
1262
+ _uid(prefix) {
1263
+ return `${prefix}-${this._idCounter++}`;
1264
+ }
1265
+
1266
+ _isItemActive(item) {
1267
+ if (item.selected) return true;
1268
+ if (item.href) {
1269
+ const loc = window.location;
1270
+ const current = loc.pathname + loc.search + loc.hash;
1271
+ return item.href === current || item.href === loc.href;
1272
+ }
1273
+ return false;
1274
+ }
1275
+
1276
+ render() {
1277
+ const isVertical = this.orientation === "vertical";
1278
+ const isCollapsed = this.collapsed && isVertical;
1279
+ const size = this.size;
1280
+ const menuDir = this.menuDirection || (isVertical ? "right" : "down");
1281
+
1282
+ const sizeConfig = {
1283
+ small: {
1284
+ padding: "var(--spacing-x-small, 4px)",
1285
+ collapsedWidth: "40px",
1286
+ bodyGap: "2px",
1287
+ buttonSize: "small",
1288
+ iconSize: "small",
1289
+ },
1290
+ medium: {
1291
+ padding: "var(--spacing-small, 6px)",
1292
+ collapsedWidth: "52px",
1293
+ bodyGap: "3px",
1294
+ buttonSize: "medium",
1295
+ iconSize: "medium",
1296
+ },
1297
+ large: {
1298
+ padding: "var(--spacing-medium, 8px)",
1299
+ collapsedWidth: "64px",
1300
+ bodyGap: "4px",
1301
+ buttonSize: "large",
1302
+ iconSize: "large",
1303
+ },
1304
+ };
1305
+ const cfg = sizeConfig[size] || sizeConfig.medium;
1306
+
1307
+ this.shadowRoot.innerHTML = "";
1308
+ this._idCounter = 0;
1309
+
1310
+ const style = document.createElement("style");
1311
+ style.textContent = `
1312
+ :host {
1313
+ display: block;
1314
+ font-family: var(--font-family-body, sans-serif);
1315
+ color: var(--component-appbar-color, #f7f7fa);
1316
+ }
1317
+
1318
+ :host([sticky]) {
1319
+ position: sticky;
1320
+ z-index: var(--component-appbar-z-index, 100);
1321
+ }
1322
+ :host([orientation="vertical"][sticky="start"]),
1323
+ :host(:not([orientation])[sticky="start"]) {
1324
+ left: 0;
1325
+ top: 0;
1326
+ height: 100%;
1327
+ }
1328
+ :host([orientation="vertical"][sticky="end"]),
1329
+ :host(:not([orientation])[sticky="end"]) {
1330
+ right: 0;
1331
+ top: 0;
1332
+ height: 100%;
1333
+ }
1334
+ :host([orientation="horizontal"][sticky="start"]) {
1335
+ top: 0;
1336
+ left: 0;
1337
+ width: 100%;
1338
+ }
1339
+ :host([orientation="horizontal"][sticky="end"]) {
1340
+ bottom: 0;
1341
+ left: 0;
1342
+ width: 100%;
1343
+ }
1344
+
1345
+ :host([sticky]) .appbar {
1346
+ border-radius: 0;
1347
+ border: none;
1348
+ }
1349
+ :host([orientation="vertical"][sticky="start"]) .appbar,
1350
+ :host(:not([orientation])[sticky="start"]) .appbar {
1351
+ border-right: var(--component-appbar-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1352
+ }
1353
+ :host([orientation="vertical"][sticky="end"]) .appbar,
1354
+ :host(:not([orientation])[sticky="end"]) .appbar {
1355
+ border-left: var(--component-appbar-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1356
+ }
1357
+ :host([orientation="horizontal"][sticky="start"]) .appbar {
1358
+ border-bottom: var(--component-appbar-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1359
+ }
1360
+ :host([orientation="horizontal"][sticky="end"]) .appbar {
1361
+ border-top: var(--component-appbar-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1362
+ }
1363
+
1364
+ .appbar {
1365
+ display: flex;
1366
+ background: var(--component-appbar-background, #0c0c0d);
1367
+ border: var(--component-appbar-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1368
+ border-radius: var(--component-appbar-border-radius, var(--component-sidebar-border-radius, 4px));
1369
+ overflow: visible;
1370
+ padding: var(--_appbar-padding);
1371
+ box-sizing: border-box;
1372
+ }
1373
+
1374
+ .appbar.vertical {
1375
+ flex-direction: column;
1376
+ width: var(--component-appbar-width, 240px);
1377
+ height: 100%;
1378
+ transition: width 0.2s ease;
1379
+ }
1380
+ .appbar.vertical.collapsed {
1381
+ width: var(--_appbar-collapsed-width);
1382
+ }
1383
+
1384
+ .appbar.horizontal {
1385
+ flex-direction: row;
1386
+ width: 100%;
1387
+ height: auto;
1388
+ align-items: center;
1389
+ }
1390
+
1391
+ .appbar-header,
1392
+ .appbar-body,
1393
+ .appbar-footer {
1394
+ flex-shrink: 0;
1395
+ }
1396
+
1397
+ .appbar-body {
1398
+ flex: 1;
1399
+ overflow-y: auto;
1400
+ overflow-x: hidden;
1401
+ display: flex;
1402
+ gap: var(--_appbar-body-gap);
1403
+ }
1404
+
1405
+ .appbar.vertical .appbar-body {
1406
+ flex-direction: column;
1407
+ }
1408
+ .appbar.horizontal .appbar-body {
1409
+ flex-direction: row;
1410
+ overflow-y: hidden;
1411
+ overflow-x: auto;
1412
+ align-items: center;
1413
+ }
1414
+
1415
+ .appbar.vertical .appbar-header {
1416
+ border-bottom: var(--component-appbar-inner-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1417
+ padding: var(--_appbar-padding) 0;
1418
+ margin-bottom: var(--_appbar-padding);
1419
+ }
1420
+ .appbar.vertical .appbar-footer {
1421
+ border-top: var(--component-appbar-inner-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1422
+ padding-top: var(--_appbar-padding);
1423
+ margin-top: var(--_appbar-padding);
1424
+ }
1425
+
1426
+ .appbar.horizontal .appbar-header {
1427
+ border-right: var(--component-appbar-inner-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1428
+ padding: var(--_appbar-padding);
1429
+ padding-right: var(--spacing-x-large, 16px);
1430
+ margin-right: var(--_appbar-padding);
1431
+ }
1432
+ .appbar.horizontal .appbar-footer {
1433
+ border-left: var(--component-appbar-inner-border-width, var(--component-sidebar-border-width, 2px)) solid var(--component-appbar-border-color, #37383a);
1434
+ padding-left: var(--_appbar-padding);
1435
+ margin-left: var(--_appbar-padding);
1436
+ }
1437
+
1438
+ .header-content {
1439
+ display: flex;
1440
+ align-items: center;
1441
+ gap: var(--spacing-small, 8px);
1442
+ overflow: hidden;
1443
+ }
1444
+ .appbar.horizontal .header-content,
1445
+ .appbar.vertical .header-content {
1446
+ flex-direction: row;
1447
+ }
1448
+ .logo-wrapper {
1449
+ width: var(--_icon-col-width);
1450
+ display: flex;
1451
+ justify-content: center;
1452
+ align-items: center;
1453
+ flex-shrink: 0;
1454
+ }
1455
+
1456
+ .header-title {
1457
+ font-weight: bold;
1458
+ white-space: nowrap;
1459
+ overflow: hidden;
1460
+ text-overflow: ellipsis;
1461
+ font-size: var(--font-size-label, 0.9em);
1462
+ }
1463
+ .appbar.vertical.collapsed .header-title {
1464
+ display: none;
1465
+ }
1466
+
1467
+ .nav-item {
1468
+ display: flex;
1469
+ align-items: center;
1470
+ position: relative;
1471
+ }
1472
+ .appbar.vertical .nav-item {
1473
+ width: 100%;
1474
+ }
1475
+ .appbar.vertical .nav-item y-button {
1476
+ display: block;
1477
+ width: 100%;
1478
+ }
1479
+ .appbar.vertical .nav-item y-button::part(button),
1480
+ .appbar.vertical .appbar-footer y-button::part(button) {
1481
+ width: 100%;
1482
+ justify-content: flex-start;
1483
+ padding-left: 0;
1484
+ padding-right: 0;
1485
+ }
1486
+
1487
+ /* Fixed-width icon column — matches collapsed inner width so icons stay centred across states */
1488
+ .appbar.vertical .nav-item y-button::part(left-icon),
1489
+ .appbar.vertical .appbar-footer y-button::part(left-icon) {
1490
+ width: var(--_icon-col-width);
1491
+ display: flex;
1492
+ justify-content: center;
1493
+ flex-shrink: 0;
1494
+ }
1495
+
1496
+ .appbar.vertical .nav-item y-button::part(right-icon) {
1497
+ margin-left: auto;
1498
+ }
1499
+
1500
+ .appbar.vertical.collapsed .nav-item y-button::part(button),
1501
+ .appbar.vertical.collapsed .appbar-footer y-button::part(button) {
1502
+ min-width: 0;
1503
+ }
1504
+ .appbar.vertical.collapsed .nav-item y-button::part(label),
1505
+ .appbar.vertical.collapsed .appbar-footer y-button::part(label) {
1506
+ display: none;
1507
+ }
1508
+ .appbar.vertical.collapsed .nav-item y-button::part(right-icon),
1509
+ .appbar.vertical.collapsed .appbar-footer y-button::part(right-icon) {
1510
+ display: none;
1511
+ }
1512
+
1513
+ .appbar-footer {
1514
+ display: flex;
1515
+ flex-direction: column;
1516
+ gap: 2px;
1517
+ }
1518
+ .appbar.horizontal .appbar-footer {
1519
+ flex-direction: row;
1520
+ align-items: center;
1521
+ }
1522
+ .appbar.vertical .appbar-footer y-button {
1523
+ display: block;
1524
+ width: 100%;
1525
+ }
1526
+
1527
+ .appbar.vertical.collapsed .appbar-header,
1528
+ .appbar.vertical.collapsed .appbar-body,
1529
+ .appbar.vertical.collapsed .appbar-footer {
1530
+ align-items: center;
1531
+ }
1532
+
1533
+ ::slotted(*) {
1534
+ display: block;
1535
+ }
1536
+ `;
1537
+ this.shadowRoot.appendChild(style);
1538
+
1539
+ // Clone document stylesheets so CSS-class-based icons (e.g. Font Awesome) render in shadow DOM
1540
+ document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
1541
+ this.shadowRoot.appendChild(link.cloneNode(true));
1542
+ });
1543
+
1544
+ const bar = document.createElement("div");
1545
+ bar.className = `appbar ${isVertical ? "vertical" : "horizontal"}`;
1546
+
1547
+ if (isCollapsed) bar.classList.add("collapsed");
1548
+ bar.setAttribute("role", "navigation");
1549
+ bar.style.setProperty("--_appbar-padding", cfg.padding);
1550
+ bar.style.setProperty("--_appbar-collapsed-width", cfg.collapsedWidth);
1551
+ bar.style.setProperty("--_appbar-body-gap", cfg.bodyGap);
1552
+ bar.style.setProperty(
1553
+ "--_icon-col-width",
1554
+ `calc(${cfg.collapsedWidth} - 2 * var(--_appbar-padding) - 2 * var(--component-appbar-border-width, var(--component-sidebar-border-width, 2px)))`,
1555
+ );
1556
+
1557
+ /* --- Header: logo + title --- */
1558
+ const header = document.createElement("div");
1559
+ header.className = "appbar-header";
1560
+ header.setAttribute("part", "header");
1561
+
1562
+ const headerContent = document.createElement("div");
1563
+ headerContent.className = "header-content";
1564
+
1565
+ const logoWrapper = document.createElement("div");
1566
+ logoWrapper.className = "logo-wrapper";
1567
+
1568
+ const logoSlot = document.createElement("slot");
1569
+ logoSlot.name = "logo";
1570
+ logoWrapper.appendChild(logoSlot);
1571
+ headerContent.appendChild(logoWrapper);
1572
+
1573
+ const titleWrapper = document.createElement("div");
1574
+ titleWrapper.className = "header-title";
1575
+
1576
+ const titleSlot = document.createElement("slot");
1577
+ titleSlot.name = "title";
1578
+ titleWrapper.appendChild(titleSlot);
1579
+ headerContent.appendChild(titleWrapper);
1580
+
1581
+ const headerSlot = document.createElement("slot");
1582
+ headerSlot.name = "header";
1583
+ header.appendChild(headerContent);
1584
+ header.appendChild(headerSlot);
1585
+ bar.appendChild(header);
1586
+
1587
+ /* --- Body: y-button nav items --- */
1588
+ const body = document.createElement("div");
1589
+ body.className = "appbar-body";
1590
+ body.setAttribute("part", "body");
1591
+
1592
+ const navItems = this.items;
1593
+ navItems.forEach((item) => {
1594
+ const hasChildren = item.children?.length > 0;
1595
+ const wrapper = document.createElement("div");
1596
+ wrapper.className = "nav-item";
1597
+
1598
+ const btn = document.createElement("y-button");
1599
+ const btnId = this._uid("appbar-btn");
1600
+ btn.id = btnId;
1601
+ const isActive = this._isItemActive(item);
1602
+ btn.setAttribute("color", isActive ? "primary" : "base");
1603
+ btn.setAttribute("style-type", "flat");
1604
+ btn.setAttribute("size", cfg.buttonSize);
1605
+
1606
+ if (item.icon) {
1607
+ if (item.icon.trim().startsWith("<")) {
1608
+ const iconEl = document.createElement("span");
1609
+ iconEl.slot = "left-icon";
1610
+ iconEl.setAttribute("part", "icon");
1611
+ iconEl.innerHTML = item.icon;
1612
+ btn.appendChild(iconEl);
1613
+ } else {
1614
+ const iconEl = document.createElement("y-icon");
1615
+ iconEl.slot = "left-icon";
1616
+ iconEl.setAttribute("part", "icon");
1617
+ iconEl.setAttribute("name", item.icon);
1618
+ iconEl.setAttribute("size", cfg.iconSize);
1619
+ btn.appendChild(iconEl);
1620
+ }
1621
+ }
1622
+
1623
+ if (item.text && !isCollapsed) {
1624
+ const label = document.createTextNode(item.text);
1625
+ btn.appendChild(label);
1626
+ }
1627
+
1628
+ if (hasChildren && !isCollapsed) {
1629
+ const arrow = document.createElement("span");
1630
+ arrow.slot = "right-icon";
1631
+ arrow.innerHTML = isVertical ? chevronRight : chevronDown;
1632
+ btn.appendChild(arrow);
1633
+ }
1634
+
1635
+ if (item.href && !hasChildren) {
1636
+ btn.addEventListener("click", () => {
1637
+ window.location.href = item.href;
1638
+ });
1639
+ }
1640
+
1641
+ if (item.slot) {
1642
+ const slot = document.createElement("slot");
1643
+ slot.name = item.slot;
1644
+ slot.appendChild(btn);
1645
+ wrapper.appendChild(slot);
1646
+ } else {
1647
+ wrapper.appendChild(btn);
1648
+ }
1649
+
1650
+ if (hasChildren) {
1651
+ const menu = document.createElement("y-menu");
1652
+ menu.setAttribute("anchor", btnId);
1653
+ menu.setAttribute("direction", menuDir);
1654
+ menu.setAttribute("size", cfg.buttonSize);
1655
+ menu.items = item.children;
1656
+ wrapper.appendChild(menu);
1657
+ }
1658
+
1659
+ body.appendChild(wrapper);
1660
+ });
1661
+
1662
+ bar.appendChild(body);
1663
+
1664
+ /* --- Footer: slot + collapse toggle (vertical only) --- */
1665
+ const footer = document.createElement("div");
1666
+ footer.className = "appbar-footer";
1667
+ footer.setAttribute("part", "footer");
1668
+
1669
+ const footerSlot = document.createElement("slot");
1670
+ footerSlot.name = "footer";
1671
+ footer.appendChild(footerSlot);
1672
+
1673
+ if (isVertical) {
1674
+ const collapseBtn = document.createElement("y-button");
1675
+ collapseBtn.setAttribute("color", "base");
1676
+ collapseBtn.setAttribute("style-type", "flat");
1677
+ collapseBtn.setAttribute("size", cfg.buttonSize);
1678
+ collapseBtn.setAttribute(
1679
+ "aria-label",
1680
+ isCollapsed ? "Expand sidebar" : "Collapse sidebar",
1681
+ );
1682
+ collapseBtn.className = "collapse-btn";
1683
+
1684
+ const collapseIcon = document.createElement("span");
1685
+ collapseIcon.slot = "left-icon";
1686
+ collapseIcon.innerHTML = isCollapsed ? expandRight : collapseLeft;
1687
+ collapseBtn.appendChild(collapseIcon);
1688
+
1689
+ if (!isCollapsed) {
1690
+ const collapseLabel = document.createTextNode("Collapse");
1691
+ collapseBtn.appendChild(collapseLabel);
1692
+ }
1693
+
1694
+ collapseBtn.addEventListener("click", this._onCollapseClick);
1695
+ footer.appendChild(collapseBtn);
1696
+ }
1697
+
1698
+ bar.appendChild(footer);
1699
+ this.shadowRoot.appendChild(bar);
1700
+ }
1701
+ }
1702
+
1703
+ if (!customElements.get("y-appbar")) {
1704
+ customElements.define("y-appbar", YumeAppbar);
1705
+ }
1706
+
1707
+ export { YumeAppbar };