@studious-creative/yumekit 0.1.7 → 0.1.8
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.
- package/dist/components/y-appbar.js +298 -4
- package/dist/components/y-icon.d.ts +3 -0
- package/dist/components/y-icon.js +29 -4
- package/dist/components/y-toast.d.ts +1 -1
- package/dist/components/y-toast.js +5 -3
- package/dist/icons/all.js +67 -28
- package/dist/index.js +491 -318
- package/dist/yumekit.min.js +1 -1
- package/package.json +1 -1
|
@@ -554,6 +554,292 @@ if (!customElements.get("y-button")) {
|
|
|
554
554
|
customElements.define("y-button", YumeButton);
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
+
/**
|
|
558
|
+
* Icon registry — a runtime map of icon names to SVG markup strings.
|
|
559
|
+
*
|
|
560
|
+
* Register only the icons you need for tree-shaking:
|
|
561
|
+
*
|
|
562
|
+
* import { registerIcon } from "@studious-creative/yumekit";
|
|
563
|
+
* registerIcon("home", homeSvgString);
|
|
564
|
+
*
|
|
565
|
+
* Or register all bundled icons at once (separate import):
|
|
566
|
+
*
|
|
567
|
+
* import "@studious-creative/yumekit/icons/all.js";
|
|
568
|
+
*/
|
|
569
|
+
|
|
570
|
+
const icons = new Map();
|
|
571
|
+
|
|
572
|
+
function getIcon(name) {
|
|
573
|
+
return icons.get(name) || "";
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Allowlist-based SVG sanitizer — only known-safe elements and attributes are kept.
|
|
577
|
+
const ALLOWED_ELEMENTS = new Set([
|
|
578
|
+
"svg",
|
|
579
|
+
"g",
|
|
580
|
+
"path",
|
|
581
|
+
"circle",
|
|
582
|
+
"ellipse",
|
|
583
|
+
"rect",
|
|
584
|
+
"line",
|
|
585
|
+
"polyline",
|
|
586
|
+
"polygon",
|
|
587
|
+
"text",
|
|
588
|
+
"tspan",
|
|
589
|
+
"defs",
|
|
590
|
+
"clippath",
|
|
591
|
+
"mask",
|
|
592
|
+
"lineargradient",
|
|
593
|
+
"radialgradient",
|
|
594
|
+
"stop",
|
|
595
|
+
"symbol",
|
|
596
|
+
"title",
|
|
597
|
+
"desc",
|
|
598
|
+
"metadata",
|
|
599
|
+
]);
|
|
600
|
+
|
|
601
|
+
const ALLOWED_ATTRS = new Set([
|
|
602
|
+
"viewbox",
|
|
603
|
+
"xmlns",
|
|
604
|
+
"fill",
|
|
605
|
+
"stroke",
|
|
606
|
+
"stroke-width",
|
|
607
|
+
"stroke-linecap",
|
|
608
|
+
"stroke-linejoin",
|
|
609
|
+
"stroke-dasharray",
|
|
610
|
+
"stroke-dashoffset",
|
|
611
|
+
"stroke-miterlimit",
|
|
612
|
+
"stroke-opacity",
|
|
613
|
+
"fill-opacity",
|
|
614
|
+
"fill-rule",
|
|
615
|
+
"clip-rule",
|
|
616
|
+
"opacity",
|
|
617
|
+
"d",
|
|
618
|
+
"cx",
|
|
619
|
+
"cy",
|
|
620
|
+
"r",
|
|
621
|
+
"rx",
|
|
622
|
+
"ry",
|
|
623
|
+
"x",
|
|
624
|
+
"x1",
|
|
625
|
+
"x2",
|
|
626
|
+
"y",
|
|
627
|
+
"y1",
|
|
628
|
+
"y2",
|
|
629
|
+
"width",
|
|
630
|
+
"height",
|
|
631
|
+
"points",
|
|
632
|
+
"transform",
|
|
633
|
+
"id",
|
|
634
|
+
"class",
|
|
635
|
+
"clip-path",
|
|
636
|
+
"mask",
|
|
637
|
+
"offset",
|
|
638
|
+
"stop-color",
|
|
639
|
+
"stop-opacity",
|
|
640
|
+
"gradient-units",
|
|
641
|
+
"gradienttransform",
|
|
642
|
+
"gradientunits",
|
|
643
|
+
"spreadmethod",
|
|
644
|
+
"patternunits",
|
|
645
|
+
"patterntransform",
|
|
646
|
+
"font-size",
|
|
647
|
+
"font-family",
|
|
648
|
+
"font-weight",
|
|
649
|
+
"text-anchor",
|
|
650
|
+
"dominant-baseline",
|
|
651
|
+
"alignment-baseline",
|
|
652
|
+
"dx",
|
|
653
|
+
"dy",
|
|
654
|
+
"rotate",
|
|
655
|
+
"textlength",
|
|
656
|
+
"lengthadjust",
|
|
657
|
+
"display",
|
|
658
|
+
"visibility",
|
|
659
|
+
"color",
|
|
660
|
+
"vector-effect",
|
|
661
|
+
]);
|
|
662
|
+
|
|
663
|
+
function sanitizeSvg(raw) {
|
|
664
|
+
if (!raw) return "";
|
|
665
|
+
const doc = new DOMParser().parseFromString(raw, "image/svg+xml");
|
|
666
|
+
const svg = doc.querySelector("svg");
|
|
667
|
+
if (!svg) return "";
|
|
668
|
+
|
|
669
|
+
const walk = (el) => {
|
|
670
|
+
for (const child of [...el.children]) {
|
|
671
|
+
if (!ALLOWED_ELEMENTS.has(child.tagName.toLowerCase())) {
|
|
672
|
+
child.remove();
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
for (const attr of [...child.attributes]) {
|
|
676
|
+
if (!ALLOWED_ATTRS.has(attr.name.toLowerCase())) {
|
|
677
|
+
child.removeAttribute(attr.name);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
walk(child);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
// Sanitize the <svg> element's own attributes
|
|
685
|
+
for (const attr of [...svg.attributes]) {
|
|
686
|
+
if (!ALLOWED_ATTRS.has(attr.name.toLowerCase())) {
|
|
687
|
+
svg.removeAttribute(attr.name);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
walk(svg);
|
|
691
|
+
return svg.outerHTML;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Cache sanitized SVG markup per icon name to avoid repeated DOMParser + DOM-walk
|
|
695
|
+
// on every render. The cache is naturally bounded by the number of registered icons.
|
|
696
|
+
const sanitizedSvgCache = new Map();
|
|
697
|
+
|
|
698
|
+
function getCachedSvg(name) {
|
|
699
|
+
if (sanitizedSvgCache.has(name)) {
|
|
700
|
+
return sanitizedSvgCache.get(name);
|
|
701
|
+
}
|
|
702
|
+
const result = sanitizeSvg(getIcon(name));
|
|
703
|
+
sanitizedSvgCache.set(name, result);
|
|
704
|
+
return result;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
class YumeIcon extends HTMLElement {
|
|
708
|
+
static get observedAttributes() {
|
|
709
|
+
return ["name", "size", "color", "label", "weight"];
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
constructor() {
|
|
713
|
+
super();
|
|
714
|
+
this.attachShadow({ mode: "open" });
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
connectedCallback() {
|
|
718
|
+
this.render();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
722
|
+
if (oldVal === newVal) return;
|
|
723
|
+
this.render();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
get name() {
|
|
727
|
+
return this.getAttribute("name") || "";
|
|
728
|
+
}
|
|
729
|
+
set name(val) {
|
|
730
|
+
this.setAttribute("name", val);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
get size() {
|
|
734
|
+
return this.getAttribute("size") || "medium";
|
|
735
|
+
}
|
|
736
|
+
set size(val) {
|
|
737
|
+
this.setAttribute("size", val);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
get color() {
|
|
741
|
+
return this.getAttribute("color") || "";
|
|
742
|
+
}
|
|
743
|
+
set color(val) {
|
|
744
|
+
if (val) this.setAttribute("color", val);
|
|
745
|
+
else this.removeAttribute("color");
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
get label() {
|
|
749
|
+
return this.getAttribute("label") || "";
|
|
750
|
+
}
|
|
751
|
+
set label(val) {
|
|
752
|
+
if (val) this.setAttribute("label", val);
|
|
753
|
+
else this.removeAttribute("label");
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
get weight() {
|
|
757
|
+
return this.getAttribute("weight") || "";
|
|
758
|
+
}
|
|
759
|
+
set weight(val) {
|
|
760
|
+
if (val) this.setAttribute("weight", val);
|
|
761
|
+
else this.removeAttribute("weight");
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
_getColor(color) {
|
|
765
|
+
const map = {
|
|
766
|
+
base: "var(--base-content--, #f7f7fa)",
|
|
767
|
+
primary: "var(--primary-content--, #0576ff)",
|
|
768
|
+
secondary: "var(--secondary-content--, #04b8b8)",
|
|
769
|
+
success: "var(--success-content--, #2dba73)",
|
|
770
|
+
warning: "var(--warning-content--, #d17f04)",
|
|
771
|
+
error: "var(--error-content--, #b80421)",
|
|
772
|
+
help: "var(--help-content--, #5405ff)",
|
|
773
|
+
};
|
|
774
|
+
return map[color] || map.base;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
_getSize(size) {
|
|
778
|
+
const map = {
|
|
779
|
+
small: "var(--component-icon-size-small, 16px)",
|
|
780
|
+
medium: "var(--component-icon-size-medium, 24px)",
|
|
781
|
+
large: "var(--component-icon-size-large, 32px)",
|
|
782
|
+
};
|
|
783
|
+
return map[size] || map.medium;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
_getWeight(weight) {
|
|
787
|
+
const map = {
|
|
788
|
+
thin: "1",
|
|
789
|
+
regular: "1.5",
|
|
790
|
+
thick: "2",
|
|
791
|
+
};
|
|
792
|
+
return map[weight] || "";
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
render() {
|
|
796
|
+
const svg = getCachedSvg(this.name);
|
|
797
|
+
const sizeVal = this._getSize(this.size);
|
|
798
|
+
const colorVal = this.color ? this._getColor(this.color) : "inherit";
|
|
799
|
+
const weightVal = this._getWeight(this.weight);
|
|
800
|
+
const label = this.label;
|
|
801
|
+
|
|
802
|
+
if (label) {
|
|
803
|
+
this.setAttribute("role", "img");
|
|
804
|
+
this.setAttribute("aria-label", label);
|
|
805
|
+
this.removeAttribute("aria-hidden");
|
|
806
|
+
} else {
|
|
807
|
+
this.setAttribute("aria-hidden", "true");
|
|
808
|
+
this.removeAttribute("role");
|
|
809
|
+
this.removeAttribute("aria-label");
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const weightCSS = weightVal
|
|
813
|
+
? `.icon-wrapper svg,
|
|
814
|
+
.icon-wrapper svg * { stroke-width: ${weightVal} !important; }`
|
|
815
|
+
: "";
|
|
816
|
+
|
|
817
|
+
this.shadowRoot.innerHTML = `
|
|
818
|
+
<style>
|
|
819
|
+
:host {
|
|
820
|
+
display: inline-flex;
|
|
821
|
+
align-items: center;
|
|
822
|
+
justify-content: center;
|
|
823
|
+
width: ${sizeVal};
|
|
824
|
+
height: ${sizeVal};
|
|
825
|
+
color: ${colorVal};
|
|
826
|
+
line-height: 0;
|
|
827
|
+
}
|
|
828
|
+
.icon-wrapper svg {
|
|
829
|
+
width: 100%;
|
|
830
|
+
height: 100%;
|
|
831
|
+
}
|
|
832
|
+
${weightCSS}
|
|
833
|
+
</style>
|
|
834
|
+
<span class="icon-wrapper" part="icon">${svg}</span>
|
|
835
|
+
`;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (!customElements.get("y-icon")) {
|
|
840
|
+
customElements.define("y-icon", YumeIcon);
|
|
841
|
+
}
|
|
842
|
+
|
|
557
843
|
class YumeMenu extends HTMLElement {
|
|
558
844
|
static get observedAttributes() {
|
|
559
845
|
return ["items", "anchor", "visible", "direction", "size"];
|
|
@@ -1328,10 +1614,18 @@ class YumeAppbar extends HTMLElement {
|
|
|
1328
1614
|
btn.setAttribute("size", cfg.buttonSize);
|
|
1329
1615
|
|
|
1330
1616
|
if (item.icon) {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1617
|
+
if (item.icon.trim().startsWith("<")) {
|
|
1618
|
+
const iconEl = document.createElement("span");
|
|
1619
|
+
iconEl.slot = "left-icon";
|
|
1620
|
+
iconEl.innerHTML = item.icon;
|
|
1621
|
+
btn.appendChild(iconEl);
|
|
1622
|
+
} else {
|
|
1623
|
+
const iconEl = document.createElement("y-icon");
|
|
1624
|
+
iconEl.slot = "left-icon";
|
|
1625
|
+
iconEl.setAttribute("name", item.icon);
|
|
1626
|
+
iconEl.setAttribute("size", "small");
|
|
1627
|
+
btn.appendChild(iconEl);
|
|
1628
|
+
}
|
|
1335
1629
|
}
|
|
1336
1630
|
|
|
1337
1631
|
if (item.text && !isCollapsed) {
|
|
@@ -10,7 +10,10 @@ export class YumeIcon extends HTMLElement {
|
|
|
10
10
|
get color(): string;
|
|
11
11
|
set label(val: string);
|
|
12
12
|
get label(): string;
|
|
13
|
+
set weight(val: string);
|
|
14
|
+
get weight(): string;
|
|
13
15
|
_getColor(color: any): any;
|
|
14
16
|
_getSize(size: any): any;
|
|
17
|
+
_getWeight(weight: any): any;
|
|
15
18
|
render(): void;
|
|
16
19
|
}
|
|
@@ -150,7 +150,7 @@ function getCachedSvg(name) {
|
|
|
150
150
|
|
|
151
151
|
class YumeIcon extends HTMLElement {
|
|
152
152
|
static get observedAttributes() {
|
|
153
|
-
return ["name", "size", "color", "label"];
|
|
153
|
+
return ["name", "size", "color", "label", "weight"];
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
constructor() {
|
|
@@ -182,10 +182,11 @@ class YumeIcon extends HTMLElement {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
get color() {
|
|
185
|
-
return this.getAttribute("color") || "
|
|
185
|
+
return this.getAttribute("color") || "";
|
|
186
186
|
}
|
|
187
187
|
set color(val) {
|
|
188
|
-
this.setAttribute("color", val);
|
|
188
|
+
if (val) this.setAttribute("color", val);
|
|
189
|
+
else this.removeAttribute("color");
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
get label() {
|
|
@@ -196,6 +197,14 @@ class YumeIcon extends HTMLElement {
|
|
|
196
197
|
else this.removeAttribute("label");
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
get weight() {
|
|
201
|
+
return this.getAttribute("weight") || "";
|
|
202
|
+
}
|
|
203
|
+
set weight(val) {
|
|
204
|
+
if (val) this.setAttribute("weight", val);
|
|
205
|
+
else this.removeAttribute("weight");
|
|
206
|
+
}
|
|
207
|
+
|
|
199
208
|
_getColor(color) {
|
|
200
209
|
const map = {
|
|
201
210
|
base: "var(--base-content--, #f7f7fa)",
|
|
@@ -218,10 +227,20 @@ class YumeIcon extends HTMLElement {
|
|
|
218
227
|
return map[size] || map.medium;
|
|
219
228
|
}
|
|
220
229
|
|
|
230
|
+
_getWeight(weight) {
|
|
231
|
+
const map = {
|
|
232
|
+
thin: "1",
|
|
233
|
+
regular: "1.5",
|
|
234
|
+
thick: "2",
|
|
235
|
+
};
|
|
236
|
+
return map[weight] || "";
|
|
237
|
+
}
|
|
238
|
+
|
|
221
239
|
render() {
|
|
222
240
|
const svg = getCachedSvg(this.name);
|
|
223
241
|
const sizeVal = this._getSize(this.size);
|
|
224
|
-
const colorVal = this._getColor(this.color);
|
|
242
|
+
const colorVal = this.color ? this._getColor(this.color) : "inherit";
|
|
243
|
+
const weightVal = this._getWeight(this.weight);
|
|
225
244
|
const label = this.label;
|
|
226
245
|
|
|
227
246
|
if (label) {
|
|
@@ -234,6 +253,11 @@ class YumeIcon extends HTMLElement {
|
|
|
234
253
|
this.removeAttribute("aria-label");
|
|
235
254
|
}
|
|
236
255
|
|
|
256
|
+
const weightCSS = weightVal
|
|
257
|
+
? `.icon-wrapper svg,
|
|
258
|
+
.icon-wrapper svg * { stroke-width: ${weightVal} !important; }`
|
|
259
|
+
: "";
|
|
260
|
+
|
|
237
261
|
this.shadowRoot.innerHTML = `
|
|
238
262
|
<style>
|
|
239
263
|
:host {
|
|
@@ -249,6 +273,7 @@ class YumeIcon extends HTMLElement {
|
|
|
249
273
|
width: 100%;
|
|
250
274
|
height: 100%;
|
|
251
275
|
}
|
|
276
|
+
${weightCSS}
|
|
252
277
|
</style>
|
|
253
278
|
<span class="icon-wrapper" part="icon">${svg}</span>
|
|
254
279
|
`;
|
|
@@ -16,7 +16,7 @@ export class YumeToast extends HTMLElement {
|
|
|
16
16
|
* @param {string} [opts.color] — base|primary|secondary|success|warning|error|help (default base).
|
|
17
17
|
* @param {number} [opts.duration] — Override container-level duration for this toast.
|
|
18
18
|
* @param {boolean} [opts.dismissible] — Show a close button (default true).
|
|
19
|
-
* @param {string} [opts.icon] — Optional
|
|
19
|
+
* @param {string} [opts.icon] — Optional y-icon name e.g. "checkmark".
|
|
20
20
|
* @returns {HTMLElement} The toast element (for manual removal).
|
|
21
21
|
*/
|
|
22
22
|
show(opts?: {
|
|
@@ -124,7 +124,7 @@ class YumeToast extends HTMLElement {
|
|
|
124
124
|
* @param {string} [opts.color] — base|primary|secondary|success|warning|error|help (default base).
|
|
125
125
|
* @param {number} [opts.duration] — Override container-level duration for this toast.
|
|
126
126
|
* @param {boolean} [opts.dismissible] — Show a close button (default true).
|
|
127
|
-
* @param {string} [opts.icon] — Optional
|
|
127
|
+
* @param {string} [opts.icon] — Optional y-icon name e.g. "checkmark".
|
|
128
128
|
* @returns {HTMLElement} The toast element (for manual removal).
|
|
129
129
|
*/
|
|
130
130
|
show(opts = {}) {
|
|
@@ -157,8 +157,10 @@ class YumeToast extends HTMLElement {
|
|
|
157
157
|
toast.style.color = textColor;
|
|
158
158
|
|
|
159
159
|
if (icon) {
|
|
160
|
-
const iconEl = document.createElement("
|
|
161
|
-
iconEl.
|
|
160
|
+
const iconEl = document.createElement("y-icon");
|
|
161
|
+
iconEl.setAttribute("name", icon);
|
|
162
|
+
iconEl.setAttribute("size", "small");
|
|
163
|
+
iconEl.className = "toast-icon";
|
|
162
164
|
iconEl.setAttribute("part", "icon");
|
|
163
165
|
toast.appendChild(iconEl);
|
|
164
166
|
}
|