@waggylabs/yumekit 0.4.0-beta.30 → 0.4.0-beta.32

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.
@@ -4,40 +4,26 @@ import { getIcon } from '../icons/registry.js';
4
4
  class YumeButton extends HTMLElement {
5
5
  static get observedAttributes() {
6
6
  return [
7
- "left-icon",
8
- "right-icon",
9
- "color",
10
- "size",
11
- "style-type",
12
- "type",
13
- "disabled",
14
- "name",
15
- "value",
16
- "autofocus",
17
- "form",
18
- "formaction",
19
- "formenctype",
20
- "formmethod",
21
- "formnovalidate",
22
- "formtarget",
23
- "aria-label",
24
- "aria-pressed",
25
- "aria-hidden",
7
+ "left-icon", "right-icon", "color", "size", "style-type", "type",
8
+ "disabled", "name", "value", "autofocus", "form", "formaction",
9
+ "formenctype", "formmethod", "formnovalidate", "formtarget",
10
+ "aria-label", "aria-pressed", "aria-hidden",
26
11
  ];
27
12
  }
28
13
 
14
+ // -------------------------------------------------------------------------
15
+ // Lifecycle
16
+ // -------------------------------------------------------------------------
17
+
29
18
  constructor() {
30
19
  super();
31
20
  this.attachShadow({ mode: "open" });
32
- this.init();
21
+ this._init();
33
22
  }
34
23
 
35
24
  connectedCallback() {
36
- if (!this.hasAttribute("size")) {
37
- this.setAttribute("size", "medium");
38
- }
39
-
40
- this.init();
25
+ if (!this.hasAttribute("size")) this.setAttribute("size", "medium");
26
+ this._init();
41
27
  }
42
28
 
43
29
  attributeChangedCallback(name, oldValue, newValue) {
@@ -51,30 +37,56 @@ class YumeButton extends HTMLElement {
51
37
  }
52
38
  }
53
39
 
54
- this.init();
40
+ this._init();
55
41
 
56
42
  if (["color", "size", "style-type", "disabled"].includes(name)) {
57
- this.updateStyles();
43
+ this._updateStyles();
58
44
  }
59
45
  }
60
46
 
47
+ // -------------------------------------------------------------------------
48
+ // Getters / Setters
49
+ // -------------------------------------------------------------------------
50
+
51
+ /** Color theme for the button (default "base"). */
52
+ get color() { return this.getAttribute("color") || "base"; }
53
+ set color(val) { this.setAttribute("color", val); }
54
+
55
+ /** Whether the button is disabled. */
56
+ get disabled() { return this.hasAttribute("disabled"); }
57
+ set disabled(val) {
58
+ if (val) this.setAttribute("disabled", "");
59
+ else this.removeAttribute("disabled");
60
+ }
61
+
62
+ /** The form name of the button. */
63
+ get name() { return this.getAttribute("name") || ""; }
64
+ set name(val) { this.setAttribute("name", val); }
65
+
66
+ /** Size: "small" | "medium" | "large" (default "medium"). */
67
+ get size() { return this.getAttribute("size") || "medium"; }
68
+ set size(val) { this.setAttribute("size", val); }
69
+
70
+ /** Visual style: "filled" | "outlined" | "flat" (default "outlined"). */
71
+ get styleType() { return this.getAttribute("style-type") || "outlined"; }
72
+ set styleType(val) { this.setAttribute("style-type", val); }
73
+
74
+ /** Button type: "button" | "submit" | "reset" (default "button"). */
75
+ get type() { return this.getAttribute("type") || "button"; }
76
+ set type(val) { this.setAttribute("type", val); }
77
+
61
78
  /** The current selected value(s), comma-separated when 'multiple' is set. */
62
79
  get value() {
63
80
  if (this.hasAttribute("multiple")) {
64
81
  return Array.from(this.selectedValues).join(",");
65
82
  } else {
66
- return this.selectedValues.size
67
- ? Array.from(this.selectedValues)[0]
68
- : "";
83
+ return this.selectedValues.size ? Array.from(this.selectedValues)[0] : "";
69
84
  }
70
85
  }
71
-
72
86
  set value(newVal) {
73
87
  if (this.hasAttribute("multiple")) {
74
88
  if (typeof newVal === "string") {
75
- this.selectedValues = new Set(
76
- newVal.split(",").map((s) => s.trim()),
77
- );
89
+ this.selectedValues = new Set(newVal.split(",").map((s) => s.trim()));
78
90
  } else if (Array.isArray(newVal)) {
79
91
  this.selectedValues = new Set(newVal);
80
92
  }
@@ -85,10 +97,13 @@ class YumeButton extends HTMLElement {
85
97
  this.selectedValues = new Set();
86
98
  }
87
99
  }
88
-
89
100
  this.setAttribute("value", newVal);
90
101
  }
91
102
 
103
+ // -------------------------------------------------------------------------
104
+ // Public
105
+ // -------------------------------------------------------------------------
106
+
92
107
  /**
93
108
  * Sets the button options from an array of objects.
94
109
  * @param {Array<Object>} options
@@ -97,225 +112,37 @@ class YumeButton extends HTMLElement {
97
112
  this.setAttribute("options", JSON.stringify(options));
98
113
  }
99
114
 
100
- handleClick() {
101
- const detail = {};
102
- const eventType = this.getAttribute("data-event");
103
-
104
- if (this.hasAttribute("disabled") || !eventType) return;
105
-
106
- Array.from(this.attributes)
107
- .filter((attr) => attr.name.startsWith("data-detail-"))
108
- .forEach((attr) => {
109
- const key = attr.name.replace("data-detail-", "");
110
- detail[key] = attr.value;
111
- });
112
-
113
- this.dispatchEvent(
114
- new CustomEvent(eventType, {
115
- detail,
116
- bubbles: true,
117
- composed: true,
118
- }),
119
- );
120
- }
121
-
122
- init() {
123
- this.applyStyles();
124
- this.render();
125
- this.proxyNativeOnClick();
126
- this.addEventListeners();
127
- }
128
-
129
- proxyNativeOnClick() {
130
- try {
131
- Object.defineProperty(this, "onclick", {
132
- get: () => this.button.onclick,
133
- set: (value) => {
134
- this.button.onclick = value;
135
- },
136
- configurable: true,
137
- enumerable: true,
138
- });
139
- } catch (e) {
140
- console.warn("Could not redefine onclick:", e);
141
- }
142
- }
143
-
144
- updateButtonAttributes() {
145
- const attributes = YumeButton.observedAttributes;
146
-
147
- attributes.forEach((attr) => {
148
- if (this.hasAttribute(attr)) {
149
- this.button.setAttribute(attr, this.getAttribute(attr));
150
- } else {
151
- this.button.removeAttribute(attr);
152
- }
153
- });
154
- }
155
-
156
- manageSlotVisibility(slotName, selector) {
157
- const slot = slotName
158
- ? this.shadowRoot.querySelector(`slot[name="${slotName}"]`)
159
- : this.shadowRoot.querySelector("slot:not([name])");
160
- const container = this.shadowRoot.querySelector(selector);
161
-
162
- const updateVisibility = () => {
163
- const hasContent = slot
164
- .assignedNodes({ flatten: true })
165
- .some(
166
- (n) =>
167
- !(
168
- n.nodeType === Node.TEXT_NODE &&
169
- n.textContent.trim() === ""
170
- ),
171
- );
172
- container.style.display = hasContent ? "inline-flex" : "none";
173
- };
174
-
175
- updateVisibility();
176
- slot.addEventListener("slotchange", updateVisibility);
177
- }
178
-
179
- render() {
180
- if (!this.button) {
181
- this.button = document.createElement("button");
182
- this.button.classList.add("button");
183
- this.button.setAttribute("role", "button");
184
- this.button.setAttribute("tabindex", "0");
185
- this.button.setAttribute("part", "button");
186
- this.shadowRoot.appendChild(this.button);
187
- }
188
-
189
- this.updateButtonAttributes();
190
-
191
- if (this.hasAttribute("disabled")) {
192
- this.button.setAttribute("disabled", "");
193
- this.button.setAttribute("aria-disabled", "true");
194
- } else {
195
- this.button.removeAttribute("disabled");
196
- this.button.setAttribute("aria-disabled", "false");
197
- }
198
-
199
- this.button.innerHTML = `
200
- <span class="icon left-icon" part="left-icon"><slot name="left-icon"></slot></span>
201
- <span class="label" part="label"><slot></slot></span>
202
- <span class="icon right-icon" part="right-icon"><slot name="right-icon"></slot></span>
203
- `;
204
-
205
- this.manageSlotVisibility("left-icon", ".left-icon");
206
- this.manageSlotVisibility("right-icon", ".right-icon");
207
- this.manageSlotVisibility("", ".label");
208
- }
209
-
210
- applyStyles() {
211
- const style = document.createElement("style");
212
- style.textContent = `
213
- :host {
214
- display: inline-block;
215
- }
216
-
217
- @font-face {
218
- font-family: "Lexend";
219
- font-display: swap;
220
- }
221
-
222
- .button {
223
- box-sizing: border-box;
224
- display: inline-flex;
225
- width: 100%;
226
- min-height: var(--button-min-height, var(--sizing-medium, 40px));
227
- min-width: var(--button-min-width, var(--sizing-medium, 40px));
228
- padding: var(--button-padding, var(--component-button-padding-medium));
229
- gap: var(--button-gap, var(--component-button-padding-medium));
230
- justify-content: center;
231
- align-items: center;
232
- position: relative;
233
- overflow: hidden;
234
- border-radius: var(--component-button-border-radius-outer, 4px);
235
- border: var(--component-button-border-width, 1px) solid var(--border-color, var(--base-content--, #f7f7fa));
236
- background: var(--background-color, #0c0c0d);
237
- transition: background-color 0.1s, color 0.1s, border-color 0.1s;
238
- cursor: pointer;
239
- color: var(--text-color);
240
- font-family: var(--font-family-body, Lexend, sans-serif);
241
- font-size: var(--font-size-button, 1em);
242
- line-height: 1;
243
- }
244
-
245
- .button:disabled {
246
- opacity: 0.5;
247
- cursor: not-allowed;
248
- }
249
-
250
- .button:hover:not(:disabled),
251
- .button:hover:not(:disabled) .button-content {
252
- background: var(--hover-background-color);
253
- color: var(--hover-text-color);
254
- border-color: var(--hover-border-color);
255
- }
256
- .button:focus:not(:disabled),
257
- .button:focus:not(:disabled) .button-content {
258
- background: var(--focus-background-color);
259
- color: var(--focus-text-color);
260
- border-color: var(--focus-border-color);
261
- }
262
- .button:active:not(:disabled),
263
- .button:active:not(:disabled) .button-content {
264
- background: var(--active-background-color);
265
- color: var(--active-text-color);
266
- border-color: var(--active-border-color);
267
- }
268
- .icon {
269
- display: flex;
270
- min-width: 16px;
271
- min-height: 1em;
272
- justify-content: center;
273
- align-items: center;
274
- }
275
- .label {
276
- line-height: inherit;
277
- min-height: 1em;
278
- align-items: center;
279
- }
280
- `;
281
- this.shadowRoot.appendChild(style);
282
- }
115
+ // -------------------------------------------------------------------------
116
+ // Private
117
+ // -------------------------------------------------------------------------
283
118
 
284
- addEventListeners() {
119
+ _addEventListeners() {
285
120
  this.button.addEventListener("focus", () => {
286
- this.dispatchEvent(
287
- new CustomEvent("focus", { bubbles: true, composed: true }),
288
- );
121
+ this.dispatchEvent(new CustomEvent("focus", { bubbles: true, composed: true }));
289
122
  });
290
123
 
291
124
  this.button.addEventListener("blur", () => {
292
- this.dispatchEvent(
293
- new CustomEvent("blur", { bubbles: true, composed: true }),
294
- );
125
+ this.dispatchEvent(new CustomEvent("blur", { bubbles: true, composed: true }));
295
126
  });
296
127
 
297
128
  this.button.addEventListener("keydown", (event) => {
298
- this.dispatchEvent(
299
- new CustomEvent("keydown", {
300
- detail: { key: event.key, code: event.code },
301
- bubbles: true,
302
- composed: true,
303
- }),
304
- );
129
+ this.dispatchEvent(new CustomEvent("keydown", {
130
+ detail: { key: event.key, code: event.code },
131
+ bubbles: true,
132
+ composed: true,
133
+ }));
305
134
  });
306
135
 
307
136
  this.button.addEventListener("keyup", (event) => {
308
- this.dispatchEvent(
309
- new CustomEvent("keyup", {
310
- detail: { key: event.key, code: event.code },
311
- bubbles: true,
312
- composed: true,
313
- }),
314
- );
137
+ this.dispatchEvent(new CustomEvent("keyup", {
138
+ detail: { key: event.key, code: event.code },
139
+ bubbles: true,
140
+ composed: true,
141
+ }));
315
142
  });
316
143
 
317
144
  this.button.addEventListener("click", (event) => {
318
- this.handleClick();
145
+ this._handleClick();
319
146
 
320
147
  if (this.getAttribute("type") === "submit") {
321
148
  const form = this.closest("form");
@@ -327,249 +154,6 @@ class YumeButton extends HTMLElement {
327
154
  });
328
155
  }
329
156
 
330
- updateStyles() {
331
- const color = this.getAttribute("color") || "base";
332
- const size = this.getAttribute("size") || "medium";
333
- const styleType = this.getAttribute("style-type") || "outlined";
334
-
335
- const colorVars = {
336
- primary: [
337
- "--primary-content--",
338
- "--primary-content-hover",
339
- "--primary-content-active",
340
- "--primary-background-component",
341
- "--primary-background-hover",
342
- "--primary-background-active",
343
- "--primary-content-inverse",
344
- ],
345
- secondary: [
346
- "--secondary-content--",
347
- "--secondary-content-hover",
348
- "--secondary-content-active",
349
- "--secondary-background-component",
350
- "--secondary-background-hover",
351
- "--secondary-background-active",
352
- "--secondary-content-inverse",
353
- ],
354
- base: [
355
- "--base-content--",
356
- "--base-content-lighter",
357
- "--base-content-lightest",
358
- "--base-background-component",
359
- "--base-background-hover",
360
- "--base-background-active",
361
- "--base-content-inverse",
362
- ],
363
- success: [
364
- "--success-content--",
365
- "--success-content-hover",
366
- "--success-content-active",
367
- "--success-background-component",
368
- "--success-background-hover",
369
- "--success-background-active",
370
- "--success-content-inverse",
371
- ],
372
- error: [
373
- "--error-content--",
374
- "--error-content-hover",
375
- "--error-content-active",
376
- "--error-background-component",
377
- "--error-background-hover",
378
- "--error-background-active",
379
- "--error-content-inverse",
380
- ],
381
- warning: [
382
- "--warning-content--",
383
- "--warning-content-hover",
384
- "--warning-content-active",
385
- "--warning-background-component",
386
- "--warning-background-hover",
387
- "--warning-background-active",
388
- "--warning-content-inverse",
389
- ],
390
- help: [
391
- "--help-content--",
392
- "--help-content-hover",
393
- "--help-content-active",
394
- "--help-background-component",
395
- "--help-background-hover",
396
- "--help-background-active",
397
- "--help-content-inverse",
398
- ],
399
- };
400
-
401
- const sizeVars = {
402
- small: [
403
- "--component-button-padding-small",
404
- "--component-button-padding-small",
405
- ],
406
- medium: [
407
- "--component-button-padding-medium",
408
- "--component-button-padding-medium",
409
- ],
410
- large: [
411
- "--component-button-padding-large",
412
- "--component-button-padding-large",
413
- ],
414
- };
415
-
416
- // Custom CSS color (hex, rgb/rgba, hsl/hsla)
417
- if (
418
- !colorVars[color] &&
419
- color &&
420
- (color.startsWith("#") ||
421
- color.startsWith("rgb") ||
422
- color.startsWith("hsl"))
423
- ) {
424
- this._applyCustomColorStyles(color, styleType, size);
425
- return;
426
- }
427
-
428
- const styleVars = {
429
- outlined: {
430
- "--background-color": `var(${colorVars[color][3]}, #0c0c0d)`,
431
- "--border-color": `var(${colorVars[color][0]}, #f7f7fa)`,
432
- "--text-color": `var(${colorVars[color][0]}, #f7f7fa)`,
433
- },
434
- filled: {
435
- "--background-color": `var(${colorVars[color][0]}, #f7f7fa)`,
436
- "--border-color": `var(${colorVars[color][0]}, #f7f7fa)`,
437
- "--text-color": `var(${colorVars[color][6]}, #0c0c0d)`,
438
- },
439
- flat: {
440
- "--background-color": `var(${colorVars[color][3]},#0c0c0d)`,
441
- "--border-color": `var(${colorVars[color][3]},#0c0c0d)`,
442
- "--text-color": `var(${colorVars[color][0]},#f7f7fa)`,
443
- },
444
- };
445
-
446
- const currentStyle = styleVars[styleType] || styleVars.outlined;
447
- Object.entries(currentStyle).forEach(([key, value]) => {
448
- this.button.style.setProperty(key, value);
449
- });
450
-
451
- if (styleType === "filled") {
452
- this.button.style.setProperty(
453
- "--hover-background-color",
454
- `var(${colorVars[color][1]}, #292a2b)`,
455
- );
456
- this.button.style.setProperty(
457
- "--hover-text-color",
458
- `var(${colorVars[color][6]}, #0c0c0d)`,
459
- );
460
- this.button.style.setProperty(
461
- "--hover-border-color",
462
- `var(${colorVars[color][1]}, #292a2b)`,
463
- );
464
- this.button.style.setProperty(
465
- "--focus-background-color",
466
- `var(${colorVars[color][2]}, #46474a)`,
467
- );
468
- this.button.style.setProperty(
469
- "--focus-text-color",
470
- `var(${colorVars[color][6]}, #0c0c0d)`,
471
- );
472
- this.button.style.setProperty(
473
- "--focus-border-color",
474
- `var(${colorVars[color][2]}, #46474a)`,
475
- );
476
- this.button.style.setProperty(
477
- "--active-background-color",
478
- `var(${colorVars[color][3]}, #0c0c0d)`,
479
- );
480
- this.button.style.setProperty(
481
- "--active-text-color",
482
- `var(${colorVars[color][0]}, #f7f7fa)`,
483
- );
484
- this.button.style.setProperty(
485
- "--active-border-color",
486
- `var(${colorVars[color][3]}, #0c0c0d)`,
487
- );
488
- } else {
489
- const borderColor = `var(${colorVars[color][0]}, #f7f7fa)`;
490
-
491
- this.button.style.setProperty(
492
- "--hover-background-color",
493
- `var(${colorVars[color][4]}, #292a2b)`,
494
- );
495
- this.button.style.setProperty(
496
- "--hover-text-color",
497
- `var(${colorVars[color][0]}, #f7f7fa)`,
498
- );
499
- this.button.style.setProperty(
500
- "--focus-background-color",
501
- `var(${colorVars[color][5]}, #46474a)`,
502
- );
503
- this.button.style.setProperty(
504
- "--focus-text-color",
505
- `var(${colorVars[color][0]}, #f7f7fa)`,
506
- );
507
- this.button.style.setProperty(
508
- "--active-background-color",
509
- `var(${colorVars[color][0]}, #f7f7fa)`,
510
- );
511
- this.button.style.setProperty(
512
- "--active-text-color",
513
- `var(${colorVars[color][6]}, #0c0c0d)`,
514
- );
515
-
516
- if (styleType === "outlined") {
517
- // Outlined buttons keep their border color across all states
518
- this.button.style.setProperty(
519
- "--hover-border-color",
520
- borderColor,
521
- );
522
- this.button.style.setProperty(
523
- "--focus-border-color",
524
- borderColor,
525
- );
526
- this.button.style.setProperty(
527
- "--active-border-color",
528
- borderColor,
529
- );
530
- } else {
531
- // Flat buttons match border to background
532
- this.button.style.setProperty(
533
- "--hover-border-color",
534
- `var(${colorVars[color][4]}, #292a2b)`,
535
- );
536
- this.button.style.setProperty(
537
- "--focus-border-color",
538
- `var(${colorVars[color][5]}, #46474a)`,
539
- );
540
- this.button.style.setProperty(
541
- "--active-border-color",
542
- `var(${colorVars[color][0]}, #f7f7fa)`,
543
- );
544
- }
545
- }
546
-
547
- const [contentPadding, buttonPadding] =
548
- sizeVars[size] || sizeVars.medium;
549
- this.button.style.setProperty(
550
- "--button-padding",
551
- `var(${buttonPadding}, var(--component-button-padding-medium))`,
552
- );
553
- this.button.style.setProperty(
554
- "--button-gap",
555
- `var(${contentPadding}, var(--component-button-padding-medium))`,
556
- );
557
-
558
- const minSizeMapping = {
559
- small: "var(--sizing-small, 32px)",
560
- medium: "var(--sizing-medium, 40px)",
561
- large: "var(--sizing-large, 56px)",
562
- };
563
- this.button.style.setProperty(
564
- "--button-min-height",
565
- minSizeMapping[size] || "40px",
566
- );
567
- this.button.style.setProperty(
568
- "--button-min-width",
569
- minSizeMapping[size] || "40px",
570
- );
571
- }
572
-
573
157
  _applyCustomColorStyles(color, styleType, size) {
574
158
  const text = contrastTextColor(color);
575
159
  const hover = `color-mix(in srgb, ${color} 85%, black)`;
@@ -625,22 +209,293 @@ class YumeButton extends HTMLElement {
625
209
  ([key, val]) => this.button.style.setProperty(key, val),
626
210
  );
627
211
 
212
+ this._applySizeStyles(size);
213
+ }
214
+
215
+ _applyFilledInteractionStyles(vars) {
216
+ this.button.style.setProperty("--hover-background-color", `var(${vars[1]}, #292a2b)`);
217
+ this.button.style.setProperty("--hover-text-color", `var(${vars[6]}, #0c0c0d)`);
218
+ this.button.style.setProperty("--hover-border-color", `var(${vars[1]}, #292a2b)`);
219
+ this.button.style.setProperty("--focus-background-color", `var(${vars[2]}, #46474a)`);
220
+ this.button.style.setProperty("--focus-text-color", `var(${vars[6]}, #0c0c0d)`);
221
+ this.button.style.setProperty("--focus-border-color", `var(${vars[2]}, #46474a)`);
222
+ this.button.style.setProperty("--active-background-color", `var(${vars[3]}, #0c0c0d)`);
223
+ this.button.style.setProperty("--active-text-color", `var(${vars[0]}, #f7f7fa)`);
224
+ this.button.style.setProperty("--active-border-color", `var(${vars[3]}, #0c0c0d)`);
225
+ }
226
+
227
+ _applyInteractionStyles(vars, styleType) {
228
+ if (styleType === "filled") {
229
+ this._applyFilledInteractionStyles(vars);
230
+ } else {
231
+ this._applyUnfilledInteractionStyles(vars, styleType);
232
+ }
233
+ }
234
+
235
+ _applySizeStyles(size) {
628
236
  const sizeVars = {
629
237
  small: "--component-button-padding-small",
630
238
  medium: "--component-button-padding-medium",
631
239
  large: "--component-button-padding-large",
632
240
  };
633
241
  const pad = sizeVars[size] || sizeVars.medium;
634
- this.button.style.setProperty("--button-padding", `var(${pad})`);
635
- this.button.style.setProperty("--button-gap", `var(${pad})`);
242
+ this.button.style.setProperty("--button-padding", `var(${pad}, var(--component-button-padding-medium))`);
243
+ this.button.style.setProperty("--button-gap", `var(${pad}, var(--component-button-padding-medium))`);
636
244
 
637
- const minSize = {
245
+ const minSizeMapping = {
638
246
  small: "var(--sizing-small, 32px)",
639
247
  medium: "var(--sizing-medium, 40px)",
640
248
  large: "var(--sizing-large, 56px)",
641
- }[size] || "var(--sizing-medium, 40px)";
642
- this.button.style.setProperty("--button-min-height", minSize);
643
- this.button.style.setProperty("--button-min-width", minSize);
249
+ };
250
+ this.button.style.setProperty("--button-min-height", minSizeMapping[size] || "40px");
251
+ this.button.style.setProperty("--button-min-width", minSizeMapping[size] || "40px");
252
+ }
253
+
254
+ _applyStyles() {
255
+ const style = document.createElement("style");
256
+ style.textContent = `
257
+ :host {
258
+ display: inline-block;
259
+ }
260
+
261
+ @font-face {
262
+ font-family: "Lexend";
263
+ font-display: swap;
264
+ }
265
+
266
+ .button {
267
+ box-sizing: border-box;
268
+ display: inline-flex;
269
+ width: 100%;
270
+ min-height: var(--button-min-height, var(--sizing-medium, 40px));
271
+ min-width: var(--button-min-width, var(--sizing-medium, 40px));
272
+ padding: var(--button-padding, var(--component-button-padding-medium));
273
+ gap: var(--button-gap, var(--component-button-padding-medium));
274
+ justify-content: center;
275
+ align-items: center;
276
+ position: relative;
277
+ overflow: hidden;
278
+ border-radius: var(--component-button-border-radius-outer, 4px);
279
+ border: var(--component-button-border-width, 1px) solid var(--border-color, var(--base-content--, #f7f7fa));
280
+ background: var(--background-color, #0c0c0d);
281
+ transition: background-color 0.1s, color 0.1s, border-color 0.1s;
282
+ cursor: pointer;
283
+ color: var(--text-color);
284
+ font-family: var(--font-family-body, Lexend, sans-serif);
285
+ font-size: var(--font-size-button, 1em);
286
+ line-height: 1;
287
+ }
288
+
289
+ .button:disabled {
290
+ opacity: 0.5;
291
+ cursor: not-allowed;
292
+ }
293
+
294
+ .button:hover:not(:disabled),
295
+ .button:hover:not(:disabled) .button-content {
296
+ background: var(--hover-background-color);
297
+ color: var(--hover-text-color);
298
+ border-color: var(--hover-border-color);
299
+ }
300
+ .button:focus:not(:disabled),
301
+ .button:focus:not(:disabled) .button-content {
302
+ background: var(--focus-background-color);
303
+ color: var(--focus-text-color);
304
+ border-color: var(--focus-border-color);
305
+ }
306
+ .button:active:not(:disabled),
307
+ .button:active:not(:disabled) .button-content {
308
+ background: var(--active-background-color);
309
+ color: var(--active-text-color);
310
+ border-color: var(--active-border-color);
311
+ }
312
+ .icon {
313
+ display: flex;
314
+ min-width: 16px;
315
+ min-height: 1em;
316
+ justify-content: center;
317
+ align-items: center;
318
+ }
319
+ .label {
320
+ line-height: inherit;
321
+ min-height: 1em;
322
+ align-items: center;
323
+ }
324
+ `;
325
+ this.shadowRoot.appendChild(style);
326
+ }
327
+
328
+ _applyUnfilledInteractionStyles(vars, styleType) {
329
+ const borderColor = `var(${vars[0]}, #f7f7fa)`;
330
+
331
+ this.button.style.setProperty("--hover-background-color", `var(${vars[4]}, #292a2b)`);
332
+ this.button.style.setProperty("--hover-text-color", `var(${vars[0]}, #f7f7fa)`);
333
+ this.button.style.setProperty("--focus-background-color", `var(${vars[5]}, #46474a)`);
334
+ this.button.style.setProperty("--focus-text-color", `var(${vars[0]}, #f7f7fa)`);
335
+ this.button.style.setProperty("--active-background-color", `var(${vars[0]}, #f7f7fa)`);
336
+ this.button.style.setProperty("--active-text-color", `var(${vars[6]}, #0c0c0d)`);
337
+
338
+ if (styleType === "outlined") {
339
+ // Outlined buttons keep their border color across all states
340
+ this.button.style.setProperty("--hover-border-color", borderColor);
341
+ this.button.style.setProperty("--focus-border-color", borderColor);
342
+ this.button.style.setProperty("--active-border-color", borderColor);
343
+ } else {
344
+ // Flat buttons match border to background
345
+ this.button.style.setProperty("--hover-border-color", `var(${vars[4]}, #292a2b)`);
346
+ this.button.style.setProperty("--focus-border-color", `var(${vars[5]}, #46474a)`);
347
+ this.button.style.setProperty("--active-border-color", `var(${vars[0]}, #f7f7fa)`);
348
+ }
349
+ }
350
+
351
+ _getColorVarsMap() {
352
+ return {
353
+ primary: ["--primary-content--", "--primary-content-hover", "--primary-content-active", "--primary-background-component", "--primary-background-hover", "--primary-background-active", "--primary-content-inverse"],
354
+ secondary: ["--secondary-content--", "--secondary-content-hover", "--secondary-content-active", "--secondary-background-component", "--secondary-background-hover", "--secondary-background-active", "--secondary-content-inverse"],
355
+ base: ["--base-content--", "--base-content-lighter", "--base-content-lightest", "--base-background-component", "--base-background-hover", "--base-background-active", "--base-content-inverse"],
356
+ success: ["--success-content--", "--success-content-hover", "--success-content-active", "--success-background-component", "--success-background-hover", "--success-background-active", "--success-content-inverse"],
357
+ error: ["--error-content--", "--error-content-hover", "--error-content-active", "--error-background-component", "--error-background-hover", "--error-background-active", "--error-content-inverse"],
358
+ warning: ["--warning-content--", "--warning-content-hover", "--warning-content-active", "--warning-background-component", "--warning-background-hover", "--warning-background-active", "--warning-content-inverse"],
359
+ help: ["--help-content--", "--help-content-hover", "--help-content-active", "--help-background-component", "--help-background-hover", "--help-background-active", "--help-content-inverse"],
360
+ };
361
+ }
362
+
363
+ _handleClick() {
364
+ const detail = {};
365
+ const eventType = this.getAttribute("data-event");
366
+
367
+ if (this.hasAttribute("disabled") || !eventType) return;
368
+
369
+ Array.from(this.attributes)
370
+ .filter((attr) => attr.name.startsWith("data-detail-"))
371
+ .forEach((attr) => {
372
+ const key = attr.name.replace("data-detail-", "");
373
+ detail[key] = attr.value;
374
+ });
375
+
376
+ this.dispatchEvent(new CustomEvent(eventType, {
377
+ detail,
378
+ bubbles: true,
379
+ composed: true,
380
+ }));
381
+ }
382
+
383
+ _init() {
384
+ this._applyStyles();
385
+ this._render();
386
+ this._proxyNativeOnClick();
387
+ this._addEventListeners();
388
+ }
389
+
390
+ _manageSlotVisibility(slotName, selector) {
391
+ const slot = slotName
392
+ ? this.shadowRoot.querySelector(`slot[name="${slotName}"]`)
393
+ : this.shadowRoot.querySelector("slot:not([name])");
394
+ const container = this.shadowRoot.querySelector(selector);
395
+
396
+ const updateVisibility = () => {
397
+ const hasContent = slot
398
+ .assignedNodes({ flatten: true })
399
+ .some((n) => !(n.nodeType === Node.TEXT_NODE && n.textContent.trim() === ""));
400
+ container.style.display = hasContent ? "inline-flex" : "none";
401
+ };
402
+
403
+ updateVisibility();
404
+ slot.addEventListener("slotchange", updateVisibility);
405
+ }
406
+
407
+ _proxyNativeOnClick() {
408
+ try {
409
+ Object.defineProperty(this, "onclick", {
410
+ get: () => this.button.onclick,
411
+ set: (value) => { this.button.onclick = value; },
412
+ configurable: true,
413
+ enumerable: true,
414
+ });
415
+ } catch (e) {
416
+ console.warn("Could not redefine onclick:", e);
417
+ }
418
+ }
419
+
420
+ _render() {
421
+ if (!this.button) {
422
+ this.button = document.createElement("button");
423
+ this.button.classList.add("button");
424
+ this.button.setAttribute("role", "button");
425
+ this.button.setAttribute("tabindex", "0");
426
+ this.button.setAttribute("part", "button");
427
+ this.shadowRoot.appendChild(this.button);
428
+ }
429
+
430
+ this._updateButtonAttributes();
431
+
432
+ if (this.hasAttribute("disabled")) {
433
+ this.button.setAttribute("disabled", "");
434
+ this.button.setAttribute("aria-disabled", "true");
435
+ } else {
436
+ this.button.removeAttribute("disabled");
437
+ this.button.setAttribute("aria-disabled", "false");
438
+ }
439
+
440
+ this.button.innerHTML = `
441
+ <span class="icon left-icon" part="left-icon"><slot name="left-icon"></slot></span>
442
+ <span class="label" part="label"><slot></slot></span>
443
+ <span class="icon right-icon" part="right-icon"><slot name="right-icon"></slot></span>
444
+ `;
445
+
446
+ this._manageSlotVisibility("left-icon", ".left-icon");
447
+ this._manageSlotVisibility("right-icon", ".right-icon");
448
+ this._manageSlotVisibility("", ".label");
449
+ }
450
+
451
+ _updateButtonAttributes() {
452
+ const attributes = YumeButton.observedAttributes;
453
+
454
+ attributes.forEach((attr) => {
455
+ if (this.hasAttribute(attr)) {
456
+ this.button.setAttribute(attr, this.getAttribute(attr));
457
+ } else {
458
+ this.button.removeAttribute(attr);
459
+ }
460
+ });
461
+ }
462
+
463
+ _updateStyles() {
464
+ const { color, size, styleType } = this;
465
+ const colorVars = this._getColorVarsMap();
466
+
467
+ if (!colorVars[color] && color && (color.startsWith("#") || color.startsWith("rgb") || color.startsWith("hsl"))) {
468
+ this._applyCustomColorStyles(color, styleType, size);
469
+ return;
470
+ }
471
+
472
+ const vars = colorVars[color];
473
+
474
+ const styleVars = {
475
+ outlined: {
476
+ "--background-color": `var(${vars[3]}, #0c0c0d)`,
477
+ "--border-color": `var(${vars[0]}, #f7f7fa)`,
478
+ "--text-color": `var(${vars[0]}, #f7f7fa)`,
479
+ },
480
+ filled: {
481
+ "--background-color": `var(${vars[0]}, #f7f7fa)`,
482
+ "--border-color": `var(${vars[0]}, #f7f7fa)`,
483
+ "--text-color": `var(${vars[6]}, #0c0c0d)`,
484
+ },
485
+ flat: {
486
+ "--background-color": `var(${vars[3]},#0c0c0d)`,
487
+ "--border-color": `var(${vars[3]},#0c0c0d)`,
488
+ "--text-color": `var(${vars[0]},#f7f7fa)`,
489
+ },
490
+ };
491
+
492
+ const currentStyle = styleVars[styleType] || styleVars.outlined;
493
+ Object.entries(currentStyle).forEach(([key, value]) => {
494
+ this.button.style.setProperty(key, value);
495
+ });
496
+
497
+ this._applyInteractionStyles(vars, styleType);
498
+ this._applySizeStyles(size);
644
499
  }
645
500
  }
646
501
 
@@ -804,13 +659,13 @@ class YumeIcon extends HTMLElement {
804
659
 
805
660
  _getColor(color) {
806
661
  const map = {
807
- base: "var(--base-content--, #f7f7fa)",
808
- primary: "var(--primary-content--, #0576ff)",
662
+ base: "var(--base-content--, #f7f7fa)",
663
+ primary: "var(--primary-content--, #0576ff)",
809
664
  secondary: "var(--secondary-content--, #04b8b8)",
810
- success: "var(--success-content--, #2dba73)",
811
- warning: "var(--warning-content--, #d17f04)",
812
- error: "var(--error-content--, #b80421)",
813
- help: "var(--help-content--, #5405ff)",
665
+ success: "var(--success-content--, #2dba73)",
666
+ warning: "var(--warning-content--, #d17f04)",
667
+ error: "var(--error-content--, #b80421)",
668
+ help: "var(--help-content--, #5405ff)",
814
669
  };
815
670
  if (map[color]) return map[color];
816
671
  if (color && (color.startsWith("#") || color.startsWith("rgb") || color.startsWith("hsl"))) {
@@ -822,9 +677,9 @@ class YumeIcon extends HTMLElement {
822
677
  _getSize(size) {
823
678
  const map = {
824
679
  "x-small": "var(--component-icon-size-x-small, 10px)",
825
- small: "var(--component-icon-size-small, 14px)",
826
- medium: "var(--component-icon-size-medium, 18px)",
827
- large: "var(--component-icon-size-large, 22px)",
680
+ small: "var(--component-icon-size-small, 14px)",
681
+ medium: "var(--component-icon-size-medium, 18px)",
682
+ large: "var(--component-icon-size-large, 22px)",
828
683
  "x-large": "var(--component-icon-size-x-large, 28px)",
829
684
  };
830
685
  return map[size] || map.medium;