tosijs-ui 1.5.8 → 1.5.10

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/icons.d.ts CHANGED
@@ -8,8 +8,6 @@ export interface IconRule {
8
8
  prefix: string | RegExp;
9
9
  apply: (baseName: string, match: RegExpMatchArray | string, parts: ElementPart[]) => Element | string | null;
10
10
  }
11
- /** Returns icon name safe for suffix concatenation (appends _ if name ends in digit) */
12
- export declare const safeIconSuffix: (name: string) => string;
13
11
  export declare const iconRules: IconRule[];
14
12
  export declare const icons: SVGIconMap;
15
13
  export declare class SvgIcon extends WebComponent {
package/dist/icons.js CHANGED
@@ -7,26 +7,6 @@
7
7
  <tosi-icon title="tosi-platform" icon="tosiPlatform" style="--tosi-icon-size: 128px"></tosi-icon>
8
8
  </div>
9
9
 
10
- A library that provides `ElementCreator` functions that produce SVG icons. It leverages `tosijs`'s
11
- `svgElements` proxy and is intended to address all the key use-cases for SVG icons in web
12
- applications along with being very easy to extend and maintain.
13
-
14
- > ### Supported Use Cases
15
- > - inline SVGs that can be styled by CSS (for buttons, etc.)
16
- > - allows both stroked and filled icons (unlike font-based systems)
17
- > - support for color icons (without requiring multiple glyphs perfectly aligned)
18
- > - icons can be rendered as data urls, e.g. to insert into CSS… (the little `owl` logo rendered under blockquotes is an example)
19
- > - icon composition: stack, transform, and style icons via naming conventions (e.g. `lock50s75o$shield`)
20
- > - extensible rules system for custom prefixes and overlays (e.g. `unLock`, `checkFile`)
21
- > - icon redirects eliminate redundant SVG data (e.g. `chevronDown` → `chevronRight90r`)
22
-
23
- ### Nice Features
24
- > - no build process magic needed (your icons are "just javascript", no special CSS files needed, no magic glyph mappings). Adding new, or overriding existing, icons is trivial.
25
- > - icons are just regular SVG, not a specialized subset.
26
- > - highly optimized and compressible (the code is comparable in size to what you get with a compressed font built from the same icons, except icon fonts don't support strokes, gradients, etc.)
27
-
28
- ## icons
29
-
30
10
  `icons` is a proxy that generates an `ElementCreator` for a given icon on demand,
31
11
  e.g. `icons.chevronDown()` produces an `<svg>` element containing a downward-pointing chevron
32
12
  icon with the class `icon-chevron-down`.
@@ -351,7 +331,7 @@ that, for example, treat all colored icons inside buttons the same way.
351
331
  <tosi-icon icon="lock50s75o_10y$shield_brandColorS" size=128></tosi-icon>
352
332
  <tosi-icon icon="unLock_brandColorS" size=128></tosi-icon>
353
333
  <tosi-icon icon="checkFile" size=128></tosi-icon>
354
- <tosi-icon icon="spin120Loader40s_30x$cloud" size=128></tosi-icon>
334
+ <tosi-icon icon="spin120Loader40s_30x_brandColorS$cloud" size=128></tosi-icon>
355
335
 
356
336
  ### Why?
357
337
 
@@ -371,6 +351,12 @@ variations on every icon?
371
351
  <tosi-icon icon="pin0f_brandColorS" size=64></tosi-icon>
372
352
  <tosi-icon icon="unPin_brandColorS" size=64></tosi-icon>
373
353
 
354
+ And now we can just create icon languages…
355
+
356
+ <tosi-icon icon="checkUsers" size=64></tosi-icon>
357
+ <tosi-icon icon="searchMap" size=64></tosi-icon>
358
+ <tosi-icon icon="cancelHeart" size=64></tosi-icon>
359
+
374
360
  ### Icon modifier suffixes
375
361
 
376
362
  The suffix system is inspired by tosijs's CSS variable math, where
@@ -460,6 +446,33 @@ through the full pipeline), an **Element** (used directly), or **null**
460
446
  },
461
447
  })
462
448
 
449
+ ### Building an icon vocabulary
450
+
451
+ You don't need to spell out the DSL every time. The real power is in
452
+ defining named patterns that become your application's icon language.
453
+ For example, a `remove` prefix that overlays a trash icon:
454
+
455
+ iconRules.push({
456
+ prefix: 'remove',
457
+ apply: (baseName) =>
458
+ `trash75o_actionColorS$${baseName}50o`,
459
+ })
460
+
461
+ Now `removeUser`, `removeFile`, `removeProject` all just work — and
462
+ they're consistent. If you redesign the trash icon or change
463
+ `--action-color`, every "remove" icon updates automatically.
464
+
465
+ You can also use `defineIcons` for one-off named compositions:
466
+
467
+ defineIcons({
468
+ cloudSync: 'spin120Loader40s_30x$cloud',
469
+ secureShield: 'lock50s75o_10y$shield',
470
+ newFile: 'plus75o60s25x25y$file',
471
+ })
472
+
473
+ Then just use `icons.cloudSync()` or `<tosi-icon icon="cloudSync">` —
474
+ the composition is invisible to consumers of the icon.
475
+
463
476
  ### Composites and `svg2DataUrl`
464
477
 
465
478
  Composed icons (stacked, overlay rules) are wrapped in a `<span>`, not
@@ -473,35 +486,6 @@ with `svg2DataUrl`.
473
486
  If you ask for an icon that isn't defined, the `icons` proxy will print a warning to console
474
487
  and render a `square` (in fact, `icons.square()`) as a fallback.
475
488
 
476
- ## Why?
477
-
478
- My evolution has been:
479
-
480
- 1. Using Icomoon.io, which I still think is a solid choice for managing custom icon fonts
481
- 2. Processing Icomoon selection.json files into icon-data and then generating SVGs dynamically
482
- from the data
483
- 3. Ingesting SVGs directly, with a little cleanup
484
-
485
- The goal is always to have a single source of truth for icons, no magic or convoluted tooling, and
486
- be able to quickly and easily add and replace icons, distribute them with components, and
487
- have no mess or fuss.
488
-
489
- 1. Works well, but…
490
- - color icons are flaky,
491
- - doesn't play well with others,
492
- - can't really distribute the icons with your components.
493
- - difficult to use icons in CSS `content`
494
- - impossible to use icons in CSS backgrounds
495
- 2. This is `icons.ts` until just now! Solves all the above, but…
496
- - no fancy SVG effects, like gradients (goodness knows I experimented with converting CSS gradients to SVG gradients) and, most
497
- - **strokes** need to be converted to outlines
498
- - outlined strokes can't be styled the way strokes can
499
- - blocks use of popular icon libraries
500
- 3. This is how everyone else works, except…
501
- - no build magic needed: `defineIcons({ myIcon: '<svg....>', ... })`
502
- - if you want build magic, `icons.js` has no dependencies, finds icons and creates an `icon-data.ts` file.
503
- - smaller icon files, even though I'm now including more icons (including *all the current* feathericons)
504
-
505
489
  ## Icon Sources
506
490
 
507
491
  Many of these icons are sourced from [Feather Icons](https://github.com/feathericons/feather), but
@@ -514,6 +498,12 @@ The remaining icons I have created myself using the excellent but sometimes flaw
514
498
  [Amadine](https://apps.apple.com/us/app/amadine-vector-design-art/id1339198386?mt=12)
515
499
  and before that [Graphic](https://apps.apple.com/us/app/graphic/id404705039?mt=12).
516
500
 
501
+ ### Building icon data
502
+
503
+ Use [make-icon-data](/?make-icon-data.js) to generate icon data from SVG files.
504
+ It supports directional folder conventions to auto-generate rotated and flipped
505
+ variants, eliminating redundant SVGs.
506
+
517
507
  ### Feather Icons Copyright Notice
518
508
 
519
509
  The MIT License (MIT)
@@ -573,8 +563,8 @@ export const svg2DataUrl = (icon, fill, stroke, strokeWidth) => {
573
563
  const text = encodeURIComponent(svg.outerHTML);
574
564
  return `url(data:image/svg+xml;charset=UTF-8,${text})`;
575
565
  };
576
- /** Returns icon name safe for suffix concatenation (appends _ if name ends in digit) */
577
- export const safeIconSuffix = (name) => /\d$/.test(name) ? name + '_' : name;
566
+ // Appends _ to names ending in digits to prevent suffix ambiguity
567
+ const safeIconSuffix = (name) => /\d$/.test(name) ? name + '_' : name;
578
568
  export const iconRules = [
579
569
  {
580
570
  prefix: /^spin(_?\d+)/,
@@ -582,40 +572,38 @@ export const iconRules = [
582
572
  const dps = match[1].replace('_', '-');
583
573
  const duration = 360 / Math.abs(parseFloat(dps));
584
574
  const direction = dps.startsWith('-') ? 'reverse' : 'normal';
585
- // Strip suffixes — apply them to the wrapper, not the inner icon
586
- const parsed = parseStyleSuffixes(baseName);
587
- const iconName = parsed ? parsed.baseName : baseName;
588
- const icon = resolveIcon(iconName, []);
575
+ // Resolve baseName with suffixes intact they apply to the icon
576
+ const icon = resolveIcon(baseName, []);
589
577
  const el = icon;
590
578
  if (el.animate) {
591
- el.animate([{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }], {
579
+ const base = el.style.transform || '';
580
+ el.animate([
581
+ { transform: `${base} rotate(0deg)` },
582
+ { transform: `${base} rotate(360deg)` },
583
+ ], {
592
584
  duration: duration * 1000,
593
585
  iterations: Infinity,
594
586
  direction,
595
587
  });
596
588
  }
597
- const wrapper = wrapIcon(baseName, parts, icon);
598
- if (parsed) {
599
- Object.assign(wrapper.style, parsed.style);
600
- }
601
- return wrapper;
589
+ return wrapIcon(baseName, parts, icon);
602
590
  },
603
591
  },
604
592
  {
605
593
  prefix: 'un',
606
- apply: (baseName) => `slash25o$${safeIconSuffix(baseName)}75s75o`,
594
+ apply: (baseName) => `slash25o$${baseName}75s75o`,
607
595
  },
608
596
  {
609
597
  prefix: 'check',
610
- apply: (baseName) => `check75o_00aa00S$${safeIconSuffix(baseName)}75s50o`,
598
+ apply: (baseName) => `check75o_00aa00S$${baseName}75s50o`,
611
599
  },
612
600
  {
613
601
  prefix: 'cancel',
614
- apply: (baseName) => `x75o_cc0000S$${safeIconSuffix(baseName)}75s50o`,
602
+ apply: (baseName) => `x75o_cc0000S$${baseName}75s50o`,
615
603
  },
616
604
  {
617
605
  prefix: 'search',
618
- apply: (baseName) => `search80s30x30y$${safeIconSuffix(baseName)}50o`,
606
+ apply: (baseName) => `search80s30x30y$${baseName}50o`,
619
607
  },
620
608
  ];
621
609
  function makeIcon(spec, parts) {
@@ -699,7 +687,7 @@ function composeIcon(prop, parts) {
699
687
  // Only apply if baseName can actually resolve to an icon
700
688
  if (!canResolve(baseName))
701
689
  continue;
702
- const result = rule.apply(baseName, match, parts);
690
+ const result = rule.apply(safeIconSuffix(baseName), match, parts);
703
691
  if (typeof result === 'string')
704
692
  return resolveIcon(result, parts);
705
693
  if (result)
@@ -728,8 +716,17 @@ function parseStyleSuffixes(name) {
728
716
  if (!baseName)
729
717
  return null;
730
718
  const data = iconData;
731
- if (!data[baseName])
732
- return null;
719
+ // Accept if baseName is in iconData, or matches a composition rule prefix
720
+ if (!data[baseName]) {
721
+ const matchesRule = iconRules.some((rule) => {
722
+ if (rule.prefix instanceof RegExp) {
723
+ return rule.prefix.test(baseName);
724
+ }
725
+ return baseName.startsWith(rule.prefix) && baseName.length > rule.prefix.length;
726
+ });
727
+ if (!matchesRule)
728
+ return null;
729
+ }
733
730
  const style = {};
734
731
  const suffixes = match[0].match(/_?\d+[osxyr]|[01]f|_[a-zA-Z0-9]+[FS]|\d+W/g);
735
732
  if (!suffixes)
@@ -787,6 +784,8 @@ function parseStyleSuffixes(name) {
787
784
  return { baseName, style };
788
785
  }
789
786
  function resolveIcon(prop, parts) {
787
+ if (prop.endsWith('_'))
788
+ prop = prop.slice(0, -1);
790
789
  const data = iconData;
791
790
  // Direct match or redirect chain
792
791
  let name = prop;