tosijs-ui 1.16.16 → 1.16.18

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/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.16.18
4
+
5
+ - **A `defineIcons` name for a stacked composition draws the stack.** `defineIcons({
6
+ cloudSync: 'spin120Loader40s_30x$cloud' })` made `icons.cloudSync()` draw the placeholder
7
+ square and log "icon cloudSync does not exist", although the same string used directly as
8
+ an icon name worked. The icons page documents exactly this pattern for building an app's
9
+ icon vocabulary, so every such name was silently a square, in every version since
10
+ composites shipped. A redirect's target now goes through the whole icon pipeline, stacking
11
+ included. closes #224, reported by tosijs-virta.
12
+ - **A named composite answers to its name.** Its `data-icon` is the name you defined
13
+ (`cloudSync`), not the composition it stands for, and suffixes and rules apply to it as
14
+ to any icon (`cloudSync50o`, `spin90CloudSync`).
15
+ - **A redirect cycle says so.** `a: 'b'` with `b: 'a'` now logs "icon a redirects in a
16
+ cycle" and draws the placeholder, instead of claiming the icon does not exist; a
17
+ composite that names itself (`a: 'a$check'`) draws the placeholder in place of the
18
+ cycling layer. A long redirect chain that ends in a drawing is no longer cut off at ten
19
+ hops.
20
+ - The named-composition example on the icons page is now a live example with a test, so
21
+ the documented pattern is checked on every build.
22
+
23
+ ## 1.16.17
24
+
25
+ - **The pinned tjs-lang is 0.14.0-rc.5; it was 0.13.13.** That is the devDependency this
26
+ package builds and tests with, and `TJS_VERSION`, which sites without their own copy load
27
+ from a CDN. What it turns on for `tjs` and `ts` examples on those sites, all of it
28
+ feature-detected in 1.16.16: examples written as modules run (`export` is removed), a
29
+ console line shows its own value and offers the JavaScript it became, and a built-in
30
+ `tjs` example that declares a function gains a **Docs** tab beside its code, generated
31
+ from its signatures.
32
+ - **What can break, and how to stay where you were.** tjs-lang 0.14 refuses some code that
33
+ 0.13 accepted, with no compatibility switch: `||` in a type (a union is spelled `|`),
34
+ `unsafe var` and `unsafe eval`, and a `wasm` block that cannot compile, among others. Its
35
+ [changelog](https://github.com/tonioloewald/tjs-lang/blob/main/CHANGELOG.md) lists them
36
+ under "what breaks". A `tjs` example using one of those stops compiling on a site that
37
+ loads tjs-lang from the CDN. To keep the old transpiler, install `tjs-lang@0.13.13` in
38
+ your project: the build copies the installed tjs-lang next to your pages and the page
39
+ prefers that copy to the CDN. A site that already installs its own tjs-lang is
40
+ unaffected.
41
+ - The transpiler bundle a page with `tjs` or `ts` examples downloads is larger: 169 kB to
42
+ 195 kB gzip. `dist/iife.js` is unchanged, and pages without such examples load neither.
43
+ - The peer range is unchanged (`^0.13.1 || ^0.14.0-rc.5`).
44
+
3
45
  ## 1.16.16
4
46
 
5
47
  For tjs-lang's book (GitHub #216 item 3 and #210 item 5), plus one change to `llms.txt`.
package/dist/icons.js CHANGED
@@ -485,16 +485,36 @@ Now `removeUser`, `removeFile`, `removeProject` all just work — and
485
485
  they're consistent. If you redesign the trash icon or change
486
486
  `--action-color`, every "remove" icon updates automatically.
487
487
 
488
- You can also use `defineIcons` for one-off named compositions:
488
+ You can also use `defineIcons` for one-off named compositions. Then just
489
+ use `icons.cloudSync()` or `<tosi-icon icon="cloudSync">`: the composition
490
+ is invisible to consumers of the icon.
489
491
 
490
- defineIcons({
491
- cloudSync: 'spin120Loader40s_30x$cloud',
492
- secureShield: 'lock50s75o_10y$shield',
493
- newFile: 'plus75o60s25x25y$file',
494
- })
492
+ ```js
493
+ import { defineIcons, icons } from 'tosijs-ui'
495
494
 
496
- Then just use `icons.cloudSync()` or `<tosi-icon icon="cloudSync">` —
497
- the composition is invisible to consumers of the icon.
495
+ defineIcons({
496
+ cloudSync: 'spin120Loader40s_30x$cloud',
497
+ secureShield: 'lock50s75o_10y$shield',
498
+ newFile: 'plus75o60s25x25y$file',
499
+ })
500
+
501
+ preview.style.setProperty('--tosi-icon-size', '48px')
502
+ preview.append(icons.cloudSync(), icons.secureShield(), icons.newFile())
503
+ ```
504
+ ```test
505
+ test('named compositions draw their composites, not the placeholder', () => {
506
+ // A spin layer is a composite of its own, so count only the outer ones.
507
+ const composites = preview.querySelectorAll(':scope > .tosi-icon-composite')
508
+ expect([...composites].map((icon) => icon.dataset.icon)).toEqual([
509
+ 'cloudSync',
510
+ 'secureShield',
511
+ 'newFile',
512
+ ])
513
+ for (const composite of composites) {
514
+ expect(composite.querySelectorAll('svg').length).toBeGreaterThan(1)
515
+ }
516
+ })
517
+ ```
498
518
 
499
519
  ### Composites and `svg2DataUrl`
500
520
 
@@ -767,7 +787,8 @@ function composeIcon(prop, parts) {
767
787
  }
768
788
  return null;
769
789
  }
770
- const MAX_REDIRECTS = 10;
790
+ // Redirect names being resolved right now, so a redirect cycle ends instead of recursing.
791
+ const expanding = new Set();
771
792
  // Style suffixes — value then letter code:
772
793
  // 50o (opacity), 75s (scale), 20x (translateX%), _10y (translateY%)
773
794
  // 90r (rotate 90°), _45r (rotate -45°), 0f (flipH), 1f (flipV)
@@ -857,24 +878,41 @@ export function resolveIcon(prop, parts) {
857
878
  const data = iconData;
858
879
  // Direct match or redirect chain
859
880
  let name = prop;
860
- for (let i = 0; i < MAX_REDIRECTS; i++) {
861
- const spec = data[name];
862
- if (!spec)
863
- break;
881
+ const seen = new Set();
882
+ for (let spec = data[name]; spec; spec = data[name]) {
864
883
  if (spec.startsWith('<'))
865
884
  return makeIcon(spec, parts);
885
+ seen.add(name);
866
886
  name = spec;
887
+ if (seen.has(name)) {
888
+ console.warn(`icon ${prop} redirects in a cycle`);
889
+ return makeIcon(iconData.square, parts);
890
+ }
867
891
  }
868
892
  if (name !== prop) {
869
- const composed = composeIcon(name, parts);
870
- if (composed)
871
- return composed;
872
- const parsed = parseStyleSuffixes(name);
873
- if (parsed) {
874
- const icon = resolveIcon(parsed.baseName, parts);
875
- Object.assign(icon.style, parsed.style);
876
- return icon;
893
+ /*
894
+ A redirect's target goes through the WHOLE pipeline, stacking included. Handling rules
895
+ and suffixes here and leaving `$` to the checks below tested `prop`, which has no `$`, so
896
+ every named composite drew the placeholder (#224). `expanding` stops a redirect that
897
+ names itself (`a: 'a$b'`) from recursing forever.
898
+ */
899
+ if (!expanding.has(prop)) {
900
+ expanding.add(prop);
901
+ try {
902
+ const icon = resolveIcon(name, parts);
903
+ // A named composite answers to its name, not to the composition it stands for.
904
+ if (icon.classList.contains('tosi-icon-composite')) {
905
+ ;
906
+ icon.dataset.icon = prop;
907
+ }
908
+ return icon;
909
+ }
910
+ finally {
911
+ expanding.delete(prop);
912
+ }
877
913
  }
914
+ console.warn(`icon ${prop} redirects in a cycle`);
915
+ return makeIcon(iconData.square, parts);
878
916
  }
879
917
  // Stack: foo$bar$baz → baz on bottom, bar on top, foo on top
880
918
  if (prop.includes('$')) {