tosijs-ui 1.5.6 → 1.5.7
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/form.d.ts +1 -0
- package/dist/form.js +20 -2
- package/dist/icon-data.js +2 -2
- package/dist/icons.d.ts +2 -1
- package/dist/icons.js +171 -134
- package/dist/iife.js +29 -29
- package/dist/iife.js.map +7 -7
- package/dist/notifications.d.ts +1 -5
- package/dist/notifications.js +3 -12
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
package/dist/form.d.ts
CHANGED
|
@@ -96,6 +96,7 @@ export declare class TosiForm extends XinComponent {
|
|
|
96
96
|
}, isValid: boolean) => void;
|
|
97
97
|
connectedCallback(): void;
|
|
98
98
|
private handleElementChange;
|
|
99
|
+
private syncFieldValues;
|
|
99
100
|
private initializeNamedElements;
|
|
100
101
|
}
|
|
101
102
|
/** @deprecated Use TosiField instead */
|
package/dist/form.js
CHANGED
|
@@ -669,7 +669,8 @@ export class TosiForm extends XinComponent {
|
|
|
669
669
|
handleSubmit = (event) => {
|
|
670
670
|
event.preventDefault();
|
|
671
671
|
event.stopPropagation();
|
|
672
|
-
//
|
|
672
|
+
// Sync field values before submitting
|
|
673
|
+
this.syncFieldValues();
|
|
673
674
|
const value = this.fields;
|
|
674
675
|
this.submitCallback(value, this.isValid);
|
|
675
676
|
};
|
|
@@ -696,13 +697,30 @@ export class TosiForm extends XinComponent {
|
|
|
696
697
|
this.fields[name] = target.value;
|
|
697
698
|
}
|
|
698
699
|
};
|
|
700
|
+
syncFieldValues() {
|
|
701
|
+
const formValue = this.fields;
|
|
702
|
+
const namedElements = this.querySelectorAll('[name], [key]');
|
|
703
|
+
for (const el of namedElements) {
|
|
704
|
+
const key = el.getAttribute('name') || el.getAttribute('key');
|
|
705
|
+
if (!key)
|
|
706
|
+
continue;
|
|
707
|
+
if (formValue[key] === undefined) {
|
|
708
|
+
const val = el.value ?? el.getAttribute('value');
|
|
709
|
+
if (val != null) {
|
|
710
|
+
formValue[key] = val;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
699
715
|
initializeNamedElements() {
|
|
700
716
|
const formValue = this.fields;
|
|
701
717
|
// Handle both 'name' (formAssociated) and 'key' (tosi-field) attributes
|
|
702
718
|
const namedElements = this.querySelectorAll('[name], [key]');
|
|
703
719
|
for (const el of namedElements) {
|
|
704
720
|
const key = el.getAttribute('name') || el.getAttribute('key');
|
|
705
|
-
if (key
|
|
721
|
+
if (!key)
|
|
722
|
+
continue;
|
|
723
|
+
if (formValue[key] !== undefined) {
|
|
706
724
|
;
|
|
707
725
|
el.value = formValue[key];
|
|
708
726
|
}
|
package/dist/icon-data.js
CHANGED
|
@@ -68,7 +68,7 @@ export default {
|
|
|
68
68
|
zapOff: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><polyline points=\"12.41 6.75 13 2 10.57 4.92\"></polyline><polyline points=\"18.57 12.91 21 10 15.66 10\"></polyline><polyline points=\"8 8 3 14 12 14 11 22 16 16\"></polyline><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"></line></svg>",
|
|
69
69
|
x: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>",
|
|
70
70
|
barChart: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><line x1=\"12\" y1=\"20\" x2=\"12\" y2=\"10\"></line><line x1=\"18\" y1=\"20\" x2=\"18\" y2=\"4\"></line><line x1=\"6\" y1=\"20\" x2=\"6\" y2=\"16\"></line></svg>",
|
|
71
|
-
lock: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path></svg>",
|
|
71
|
+
lock: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path></svg> ",
|
|
72
72
|
logIn: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4\"></path><polyline points=\"10 17 15 12 10 7\"></polyline><line x1=\"15\" y1=\"12\" x2=\"3\" y2=\"12\"></line></svg>",
|
|
73
73
|
shoppingBag: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z\"></path><line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"></line><path d=\"M16 10a4 4 0 0 1-8 0\"></path></svg>",
|
|
74
74
|
divide: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"6\" r=\"2\"></circle><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line><circle cx=\"12\" cy=\"18\" r=\"2\"></circle></svg>",
|
|
@@ -142,7 +142,7 @@ export default {
|
|
|
142
142
|
rss: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M4 11a9 9 0 0 1 9 9\"></path><path d=\"M4 4a16 16 0 0 1 16 16\"></path><circle cx=\"5\" cy=\"19\" r=\"1\"></circle></svg>",
|
|
143
143
|
wifi: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M5 12.55a11 11 0 0 1 14.08 0\"></path><path d=\"M1.42 9a16 16 0 0 1 21.16 0\"></path><path d=\"M8.53 16.11a6 6 0 0 1 6.95 0\"></path><line x1=\"12\" y1=\"20\" x2=\"12.01\" y2=\"20\"></line></svg>",
|
|
144
144
|
watch: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"7\"></circle><polyline points=\"12 9 12 12 13.5 13.5\"></polyline><path d=\"M16.51 17.35l-.35 3.83a2 2 0 0 1-2 1.82H9.83a2 2 0 0 1-2-1.82l-.35-3.83m.01-10.7l.35-3.83A2 2 0 0 1 9.83 1h4.35a2 2 0 0 1 2 1.82l.35 3.83\"></path></svg>",
|
|
145
|
-
info: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"></line><line x1=\"12\" y1=\"8\" x2=\"12
|
|
145
|
+
info: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"></line><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"7.75\"></line></svg>",
|
|
146
146
|
userX: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"></path><circle cx=\"8.5\" cy=\"7\" r=\"4\"></circle><line x1=\"18\" y1=\"8\" x2=\"23\" y2=\"13\"></line><line x1=\"23\" y1=\"8\" x2=\"18\" y2=\"13\"></line></svg>",
|
|
147
147
|
loader: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"6\"></line><line x1=\"12\" y1=\"18\" x2=\"12\" y2=\"22\"></line><line x1=\"4.93\" y1=\"4.93\" x2=\"7.76\" y2=\"7.76\"></line><line x1=\"16.24\" y1=\"16.24\" x2=\"19.07\" y2=\"19.07\"></line><line x1=\"2\" y1=\"12\" x2=\"6\" y2=\"12\"></line><line x1=\"18\" y1=\"12\" x2=\"22\" y2=\"12\"></line><line x1=\"4.93\" y1=\"19.07\" x2=\"7.76\" y2=\"16.24\"></line><line x1=\"16.24\" y1=\"7.76\" x2=\"19.07\" y2=\"4.93\"></line></svg>",
|
|
148
148
|
folderPlus: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"></path><line x1=\"12\" y1=\"11\" x2=\"12\" y2=\"17\"></line><line x1=\"9\" y1=\"14\" x2=\"15\" y2=\"14\"></line></svg>",
|
package/dist/icons.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const defineIcons: (newIcons: {
|
|
|
6
6
|
export declare const svg2DataUrl: (icon: Element, fill?: string, stroke?: string, strokeWidth?: number) => string;
|
|
7
7
|
export interface IconRule {
|
|
8
8
|
prefix: string | RegExp;
|
|
9
|
-
apply: (baseName: string, match: RegExpMatchArray | string, parts: ElementPart[]) => Element | null;
|
|
9
|
+
apply: (baseName: string, match: RegExpMatchArray | string, parts: ElementPart[]) => Element | string | null;
|
|
10
10
|
}
|
|
11
11
|
export declare const iconRules: IconRule[];
|
|
12
12
|
export declare const icons: SVGIconMap;
|
|
@@ -20,6 +20,7 @@ export declare class SvgIcon extends WebComponent {
|
|
|
20
20
|
'--tosi-icon-stroke-linecap': string;
|
|
21
21
|
'--tosi-icon-fill': string;
|
|
22
22
|
display: string;
|
|
23
|
+
verticalAlign: string;
|
|
23
24
|
stroke: string;
|
|
24
25
|
strokeWidth: string;
|
|
25
26
|
strokeLinejoin: string;
|
package/dist/icons.js
CHANGED
|
@@ -347,105 +347,125 @@ that, for example, treat all colored icons inside buttons the same way.
|
|
|
347
347
|
|
|
348
348
|
## Icon Composition & Math
|
|
349
349
|
|
|
350
|
-
<tosi-icon icon="tosi$
|
|
351
|
-
<tosi-icon icon="lock50s75o_10y$
|
|
352
|
-
<tosi-icon icon="
|
|
350
|
+
<tosi-icon icon="tosi$map50o_brandColorS" size=128></tosi-icon>
|
|
351
|
+
<tosi-icon icon="lock50s75o_10y$shield_brandColorS" size=128></tosi-icon>
|
|
352
|
+
<tosi-icon icon="unLock_brandColorS" size=128></tosi-icon>
|
|
353
353
|
<tosi-icon icon="checkFile" size=128></tosi-icon>
|
|
354
|
-
<tosi-icon icon="
|
|
354
|
+
<tosi-icon icon="spin120Loader40s_30x$cloud" size=128></tosi-icon>
|
|
355
355
|
|
|
356
|
-
|
|
357
|
-
from a base icon and a prefix:
|
|
356
|
+
### Why?
|
|
358
357
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
-
|
|
362
|
-
|
|
358
|
+
I needed a pin icon for column pinning in the data table. The only pin
|
|
359
|
+
in the feather set is a map pin, so I created a push-pin icon
|
|
360
|
+
<tosi-icon icon="pin_brandColorS" size=24></tosi-icon>.
|
|
361
|
+
But immediately I also needed unpin, pin-left, and pin-right — a lot
|
|
362
|
+
of new icons for one feature. Of course I could flip the pin with CSS, but
|
|
363
|
+
this is a problem *everywhere, all the time*: every directional icon
|
|
364
|
+
needs 2–4 variants, every action needs a negation, every status needs
|
|
365
|
+
an overlay.
|
|
363
366
|
|
|
364
|
-
|
|
367
|
+
Why not fix it once and also eliminate the need to maintain trivial
|
|
368
|
+
variations on every icon?
|
|
365
369
|
|
|
366
|
-
-
|
|
367
|
-
-
|
|
368
|
-
-
|
|
369
|
-
- `search<Icon>` — magnifier overlay
|
|
370
|
+
<tosi-icon icon="pin_brandColorS" size=64></tosi-icon>
|
|
371
|
+
<tosi-icon icon="pin0f_brandColorS" size=64></tosi-icon>
|
|
372
|
+
<tosi-icon icon="unPin_brandColorS" size=64></tosi-icon>
|
|
370
373
|
|
|
371
|
-
|
|
372
|
-
overlay icon centered on top. **Overlay icons should have a square viewBox** —
|
|
373
|
-
a non-square overlay on a non-square base will produce unexpected results.
|
|
374
|
+
### Icon modifier suffixes
|
|
374
375
|
|
|
375
|
-
|
|
376
|
+
The suffix system is inspired by tosijs's CSS variable math, where
|
|
377
|
+
`borderRadius50` becomes `calc(var(--border-radius) * 0.5)` and
|
|
378
|
+
`someColor50o` adjusts opacity to 50%. The same `value + letter`
|
|
379
|
+
convention works for icons:
|
|
376
380
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
381
|
+
- `NNo` — opacity N% (e.g. `lock50o` = 50% opacity)
|
|
382
|
+
- `NNs` — scale N% (e.g. `star75s` = 75% scale)
|
|
383
|
+
- `NNr` — rotate N° (e.g. `chevronRight90r` = chevron pointing down)
|
|
384
|
+
- `_NNr` — rotate -N° (e.g. `arrow_45r`)
|
|
385
|
+
- `0f` — flip horizontally (e.g. `sidebar0f`)
|
|
386
|
+
- `1f` — flip vertically
|
|
387
|
+
- `NNx` — translateX N% (e.g. `plus20x` = shift right 20%)
|
|
388
|
+
- `NNy` — translateY N% (e.g. `plus_20y` = shift up 20%)
|
|
389
|
+
- `_<HEX>F` — fill color (e.g. `star_FF0000F` = red fill; use uppercase hex)
|
|
390
|
+
- `_<HEX>S` — stroke color (e.g. `lock_00FS` = blue stroke)
|
|
391
|
+
- `_<camelCase>F` — fill CSS variable (e.g. `star_brandColorF` = `var(--brand-color)`)
|
|
392
|
+
- `_<camelCase>S` — stroke CSS variable (e.g. `lock_accentS` = `var(--accent)`)
|
|
393
|
+
- CSS color math works too: `star_brandColor40oF` = brand color at 40% opacity
|
|
394
|
+
- `NW` — stroke width (e.g. `lock4W` = stroke-width 4)
|
|
384
395
|
|
|
385
|
-
|
|
396
|
+
Suffixes combine freely: `plus50o60s25x25y_f00F` = plus at 50% opacity,
|
|
397
|
+
60% scale, shifted 25% right and down, filled red.
|
|
386
398
|
|
|
387
|
-
|
|
388
|
-
modify existing ones, or replace them entirely:
|
|
399
|
+
### Stacking icons
|
|
389
400
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
overlayStyle: { color: 'blue', opacity: '0.75' },
|
|
395
|
-
baseStyle: { opacity: '0.5', transform: 'scale(0.75)', transformOrigin: '50% 50%' },
|
|
396
|
-
})
|
|
401
|
+
Use `$` to stack icons: `overlay$base`, or `top$middle$bottom` for
|
|
402
|
+
multiple layers. The last segment is the base (sets the size), everything
|
|
403
|
+
before it is overlaid on top. Each segment is resolved independently —
|
|
404
|
+
suffixes, redirects, and rules all work:
|
|
397
405
|
|
|
398
|
-
//
|
|
399
|
-
|
|
406
|
+
icons['tosi$map50o']() // tosi logo on a 50% opacity map
|
|
407
|
+
icons['star45r$circle']() // rotated star on a circle
|
|
408
|
+
icons['lock50s75o_10y$shield']() // translucent lock on a shield
|
|
409
|
+
icons['star25o$lock50o$shield']() // three layers
|
|
400
410
|
|
|
401
|
-
|
|
402
|
-
iconRules.length = 0
|
|
403
|
-
iconRules.push(...myCustomRules)
|
|
411
|
+
### Icon redirects
|
|
404
412
|
|
|
405
|
-
|
|
413
|
+
Icon definitions that don't start with `<svg` are treated as redirects.
|
|
414
|
+
This is how we eliminate redundant SVG files — `chevronDown` doesn't
|
|
415
|
+
need its own SVG:
|
|
406
416
|
|
|
407
|
-
|
|
408
|
-
|
|
417
|
+
defineIcons({
|
|
418
|
+
chevronDown: 'chevronRight90r',
|
|
419
|
+
chevronLeft: 'chevronRight180r',
|
|
420
|
+
chevronUp: 'chevronRight270r',
|
|
421
|
+
userAdd: 'plus50o60s25x25y$user',
|
|
422
|
+
})
|
|
409
423
|
|
|
410
|
-
|
|
411
|
-
icons['star45r$circle']() // rotated star on a circle
|
|
412
|
-
icons['lock50s75o_10y$shield']() // small translucent lock on a shield
|
|
424
|
+
### Prefix rules
|
|
413
425
|
|
|
414
|
-
|
|
415
|
-
|
|
426
|
+
Rules apply named prefixes to icons. Built-in rules use string rewrites
|
|
427
|
+
that feed back into the resolution pipeline:
|
|
416
428
|
|
|
417
|
-
|
|
429
|
+
- `un<Icon>` — translucent slash overlay (e.g. `unPin`, `unLock`)
|
|
430
|
+
- `check<Icon>` — green check overlay
|
|
431
|
+
- `cancel<Icon>` — red x overlay
|
|
432
|
+
- `search<Icon>` — magnifier overlay
|
|
433
|
+
- `spin<dps><Icon>` — continuous rotation at N°/second (e.g. `spin360Loader`)
|
|
434
|
+
- `spin_<dps><Icon>` — counter-clockwise (e.g. `spin_180Star`)
|
|
418
435
|
|
|
419
|
-
|
|
420
|
-
|
|
436
|
+
The overlay rules are just string rewrites — for example, `unFoo`
|
|
437
|
+
becomes `slash25o$foo75s75o`. **Overlay icons should have a square
|
|
438
|
+
viewBox** for best results on non-square base icons.
|
|
421
439
|
|
|
422
|
-
|
|
423
|
-
- `NNs` — scale N% (e.g. `star75s` = 75% scale)
|
|
424
|
-
- `NNx` — translateX N% (e.g. `plus20x` = shift right 20%)
|
|
425
|
-
- `NNy` — translateY N% (e.g. `plus_20y` = shift up 20%)
|
|
426
|
-
- `NNr` — rotate N° (e.g. `chevronRight90r` = chevron pointing down)
|
|
427
|
-
- `_NNr` — rotate -N° (e.g. `arrow_45r`)
|
|
428
|
-
- `0f` — flip horizontally (e.g. `sidebar0f`)
|
|
429
|
-
- `1f` — flip vertically
|
|
430
|
-
- `_<hex>F` — fill color (e.g. `star_ff0000F` = red fill, `star_f00F` = shorthand)
|
|
431
|
-
- `_<hex>S` — stroke color (e.g. `lock_00fS` = blue stroke)
|
|
432
|
-
- `NW` — stroke width (e.g. `lock4W` = stroke-width 4)
|
|
440
|
+
### Custom rules
|
|
433
441
|
|
|
434
|
-
|
|
435
|
-
|
|
442
|
+
`iconRules` is a mutable array. Each rule has a `prefix` (string or
|
|
443
|
+
RegExp) and an `apply` function that returns a **string** (resolved
|
|
444
|
+
through the full pipeline), an **Element** (used directly), or **null**
|
|
445
|
+
(skip to next rule):
|
|
436
446
|
|
|
437
|
-
|
|
447
|
+
// String rewrite: addFoo → plus75o_0000ffS$foo75s50o
|
|
448
|
+
iconRules.push({
|
|
449
|
+
prefix: 'add',
|
|
450
|
+
apply: (baseName) => `plus75o_0000ffS$${baseName}75s50o`,
|
|
451
|
+
})
|
|
438
452
|
|
|
439
|
-
|
|
440
|
-
|
|
453
|
+
// Function rule with side effects (like spin)
|
|
454
|
+
iconRules.push({
|
|
455
|
+
prefix: /^glow(\d+)/,
|
|
456
|
+
apply: (baseName, match, parts) => {
|
|
457
|
+
const icon = resolveIcon(baseName, parts)
|
|
458
|
+
icon.style.filter = `brightness(${match[1]}%)`
|
|
459
|
+
return icon
|
|
460
|
+
},
|
|
441
461
|
})
|
|
442
462
|
|
|
443
463
|
### Composites and `svg2DataUrl`
|
|
444
464
|
|
|
445
|
-
Composed icons (
|
|
446
|
-
single SVG. `svg2DataUrl()` will render only the base icon and log a
|
|
447
|
-
console error.
|
|
448
|
-
|
|
465
|
+
Composed icons (stacked, overlay rules) are wrapped in a `<span>`, not
|
|
466
|
+
a single SVG. `svg2DataUrl()` will render only the base icon and log a
|
|
467
|
+
console error. Simple suffix transforms and plain icons work normally
|
|
468
|
+
with `svg2DataUrl`.
|
|
449
469
|
|
|
450
470
|
|
|
451
471
|
## Missing Icons
|
|
@@ -492,7 +512,7 @@ organizations themselves. It's up to you to use them correctly.
|
|
|
492
512
|
|
|
493
513
|
The remaining icons I have created myself using the excellent but sometimes flawed
|
|
494
514
|
[Amadine](https://apps.apple.com/us/app/amadine-vector-design-art/id1339198386?mt=12)
|
|
495
|
-
and
|
|
515
|
+
and before that [Graphic](https://apps.apple.com/us/app/graphic/id404705039?mt=12).
|
|
496
516
|
|
|
497
517
|
### Feather Icons Copyright Notice
|
|
498
518
|
|
|
@@ -553,26 +573,6 @@ export const svg2DataUrl = (icon, fill, stroke, strokeWidth) => {
|
|
|
553
573
|
const text = encodeURIComponent(svg.outerHTML);
|
|
554
574
|
return `url(data:image/svg+xml;charset=UTF-8,${text})`;
|
|
555
575
|
};
|
|
556
|
-
// Helper for overlay-style rules
|
|
557
|
-
function overlayRule(prefix, overlay, overlayStyle, baseStyle) {
|
|
558
|
-
return {
|
|
559
|
-
prefix,
|
|
560
|
-
apply(baseName, _match, parts) {
|
|
561
|
-
const base = resolveIcon(baseName, []);
|
|
562
|
-
const over = resolveIcon(overlay, []);
|
|
563
|
-
Object.assign(base.style, baseStyle);
|
|
564
|
-
Object.assign(over.style, {
|
|
565
|
-
position: 'absolute',
|
|
566
|
-
inset: '0',
|
|
567
|
-
width: '100%',
|
|
568
|
-
height: '100%',
|
|
569
|
-
...overlayStyle,
|
|
570
|
-
});
|
|
571
|
-
return wrapIcon(baseName, parts, base, over);
|
|
572
|
-
},
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
const spinKeyframesInjected = { done: false };
|
|
576
576
|
export const iconRules = [
|
|
577
577
|
{
|
|
578
578
|
prefix: /^spin(_?\d+)/,
|
|
@@ -580,27 +580,41 @@ export const iconRules = [
|
|
|
580
580
|
const dps = match[1].replace('_', '-');
|
|
581
581
|
const duration = 360 / Math.abs(parseFloat(dps));
|
|
582
582
|
const direction = dps.startsWith('-') ? 'reverse' : 'normal';
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
583
|
+
// Strip suffixes — apply them to the wrapper, not the inner icon
|
|
584
|
+
const parsed = parseStyleSuffixes(baseName);
|
|
585
|
+
const iconName = parsed ? parsed.baseName : baseName;
|
|
586
|
+
const icon = resolveIcon(iconName, []);
|
|
587
|
+
const el = icon;
|
|
588
|
+
if (el.animate) {
|
|
589
|
+
el.animate([{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }], {
|
|
590
|
+
duration: duration * 1000,
|
|
591
|
+
iterations: Infinity,
|
|
592
|
+
direction,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
const wrapper = wrapIcon(baseName, parts, icon);
|
|
596
|
+
if (parsed) {
|
|
597
|
+
Object.assign(wrapper.style, parsed.style);
|
|
589
598
|
}
|
|
590
|
-
|
|
591
|
-
icon.style.animation =
|
|
592
|
-
`tosi-spin ${duration}s linear infinite ${direction}`;
|
|
593
|
-
return wrapIcon(baseName, parts, icon);
|
|
599
|
+
return wrapper;
|
|
594
600
|
},
|
|
595
601
|
},
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
602
|
+
{
|
|
603
|
+
prefix: 'un',
|
|
604
|
+
apply: (baseName) => `slash25o$${baseName}75s75o`,
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
prefix: 'check',
|
|
608
|
+
apply: (baseName) => `check75o_00aa00S$${baseName}75s50o`,
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
prefix: 'cancel',
|
|
612
|
+
apply: (baseName) => `x75o_cc0000S$${baseName}75s50o`,
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
prefix: 'search',
|
|
616
|
+
apply: (baseName) => `search80s30x30y$${baseName}50o`,
|
|
617
|
+
},
|
|
604
618
|
];
|
|
605
619
|
function makeIcon(spec, parts) {
|
|
606
620
|
const div = elements.div();
|
|
@@ -652,6 +666,13 @@ function wrapIcon(prop, parts, ...children) {
|
|
|
652
666
|
}
|
|
653
667
|
return wrapper;
|
|
654
668
|
}
|
|
669
|
+
function canResolve(name) {
|
|
670
|
+
const data = iconData;
|
|
671
|
+
if (data[name])
|
|
672
|
+
return true;
|
|
673
|
+
const parsed = parseStyleSuffixes(name);
|
|
674
|
+
return parsed != null && !!data[parsed.baseName];
|
|
675
|
+
}
|
|
655
676
|
function composeIcon(prop, parts) {
|
|
656
677
|
for (const rule of iconRules) {
|
|
657
678
|
let baseName;
|
|
@@ -673,7 +694,12 @@ function composeIcon(prop, parts) {
|
|
|
673
694
|
prop.slice(rule.prefix.length + 1);
|
|
674
695
|
match = rule.prefix;
|
|
675
696
|
}
|
|
697
|
+
// Only apply if baseName can actually resolve to an icon
|
|
698
|
+
if (!canResolve(baseName))
|
|
699
|
+
continue;
|
|
676
700
|
const result = rule.apply(baseName, match, parts);
|
|
701
|
+
if (typeof result === 'string')
|
|
702
|
+
return resolveIcon(result, parts);
|
|
677
703
|
if (result)
|
|
678
704
|
return result;
|
|
679
705
|
}
|
|
@@ -683,8 +709,9 @@ const MAX_REDIRECTS = 10;
|
|
|
683
709
|
// Style suffixes — always value then letter code:
|
|
684
710
|
// 50o (opacity), 75s (scale), 20x (translateX%), _10y (translateY%)
|
|
685
711
|
// 90r (rotate 90°), _45r (rotate -45°), 0f (flipH), 1f (flipV)
|
|
686
|
-
//
|
|
687
|
-
|
|
712
|
+
// _FF0000F (fill hex), _f00S (stroke hex), 3W (stroke-width)
|
|
713
|
+
// _brandColorF (fill var), _accentS (stroke var)
|
|
714
|
+
const SUFFIX_RE = /(_?\d{2,3}[osxyr]|[01]f|_[a-zA-Z0-9]+[FS]|\d{1,3}W)+$/;
|
|
688
715
|
function parseStyleSuffixes(name) {
|
|
689
716
|
const match = name.match(SUFFIX_RE);
|
|
690
717
|
if (!match)
|
|
@@ -693,7 +720,7 @@ function parseStyleSuffixes(name) {
|
|
|
693
720
|
if (!baseName)
|
|
694
721
|
return null;
|
|
695
722
|
const style = {};
|
|
696
|
-
const suffixes = match[0].match(/_?\d{2,3}[osxyr]|[01]f|_[
|
|
723
|
+
const suffixes = match[0].match(/_?\d{2,3}[osxyr]|[01]f|_[a-zA-Z0-9]+[FS]|\d{1,3}W/g);
|
|
697
724
|
let tx = '';
|
|
698
725
|
let ty = '';
|
|
699
726
|
let scale = '';
|
|
@@ -701,11 +728,16 @@ function parseStyleSuffixes(name) {
|
|
|
701
728
|
let flip = '';
|
|
702
729
|
for (const s of suffixes) {
|
|
703
730
|
const code = s[s.length - 1];
|
|
704
|
-
if (code === 'F') {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
731
|
+
if (code === 'F' || code === 'S') {
|
|
732
|
+
const raw = s.slice(1, -1);
|
|
733
|
+
const isHex = /^[0-9a-fA-F]{3,8}$/.test(raw);
|
|
734
|
+
const value = isHex ? '#' + raw : vars[raw];
|
|
735
|
+
if (code === 'F') {
|
|
736
|
+
style.fill = value;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
style.stroke = value;
|
|
740
|
+
}
|
|
709
741
|
}
|
|
710
742
|
else if (code === 'W') {
|
|
711
743
|
style.strokeWidth = s.slice(0, -1);
|
|
@@ -764,30 +796,34 @@ function resolveIcon(prop, parts) {
|
|
|
764
796
|
return icon;
|
|
765
797
|
}
|
|
766
798
|
}
|
|
767
|
-
// Stack:
|
|
799
|
+
// Stack: foo$bar$baz → baz on bottom, bar on top, foo on top
|
|
768
800
|
if (prop.includes('$')) {
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
801
|
+
const segments = prop.split('$');
|
|
802
|
+
// Last segment is the base (sets the size), rest are overlays
|
|
803
|
+
const base = resolveIcon(segments[segments.length - 1], []);
|
|
804
|
+
const overlays = segments.slice(0, -1).map((name) => {
|
|
805
|
+
const icon = resolveIcon(name, []);
|
|
806
|
+
Object.assign(icon.style, {
|
|
807
|
+
position: 'absolute',
|
|
808
|
+
inset: '0',
|
|
809
|
+
width: '100%',
|
|
810
|
+
height: '100%',
|
|
811
|
+
});
|
|
812
|
+
return icon;
|
|
777
813
|
});
|
|
778
|
-
return wrapIcon(prop, parts,
|
|
814
|
+
return wrapIcon(prop, parts, base, ...overlays);
|
|
779
815
|
}
|
|
780
|
-
//
|
|
816
|
+
// Try composition (spin, un, check, etc.)
|
|
817
|
+
const composed = composeIcon(prop, parts);
|
|
818
|
+
if (composed)
|
|
819
|
+
return composed;
|
|
820
|
+
// Style suffixes — strip them, resolve the base, apply after
|
|
781
821
|
const parsed = parseStyleSuffixes(prop);
|
|
782
822
|
if (parsed) {
|
|
783
823
|
const icon = resolveIcon(parsed.baseName, parts);
|
|
784
824
|
Object.assign(icon.style, parsed.style);
|
|
785
825
|
return icon;
|
|
786
826
|
}
|
|
787
|
-
// Try composition (spin, un, check, etc.)
|
|
788
|
-
const composed = composeIcon(prop, parts);
|
|
789
|
-
if (composed)
|
|
790
|
-
return composed;
|
|
791
827
|
if (prop) {
|
|
792
828
|
console.warn(`icon ${prop} does not exist`);
|
|
793
829
|
}
|
|
@@ -809,6 +845,7 @@ export class SvgIcon extends WebComponent {
|
|
|
809
845
|
'--tosi-icon-stroke-linecap': 'var(--icon-stroke-linecap, round)',
|
|
810
846
|
'--tosi-icon-fill': 'var(--xin-icon-fill, var(--icon-fill, none))',
|
|
811
847
|
display: 'inline-flex',
|
|
848
|
+
verticalAlign: 'text-bottom',
|
|
812
849
|
stroke: 'currentColor',
|
|
813
850
|
strokeWidth: varDefault.tosiIconStrokeWidth('2px'),
|
|
814
851
|
strokeLinejoin: varDefault.tosiIconStrokeLinejoin('round'),
|