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