tosijs-ui 1.16.33 → 1.16.34
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 +31 -0
- package/dist/icons.js +80 -14
- package/dist/iife.js +93 -93
- package/dist/iife.js.map +4 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.16.34
|
|
4
|
+
|
|
5
|
+
Spinning icons keep spinning while the page is busy, which is the only time a loader is on
|
|
6
|
+
screen. Nothing to change unless your CSS or script reaches inside a spin icon: see the last
|
|
7
|
+
bullet.
|
|
8
|
+
|
|
9
|
+
### Icons
|
|
10
|
+
|
|
11
|
+
- **`spinN<Icon>` no longer freezes on a busy page** (board #3508). The rotation was started
|
|
12
|
+
with `el.animate()` on the `<svg>`, and WebKit animates an SVG transform on the main thread,
|
|
13
|
+
so on Safari and every iOS browser a loader stood still exactly while the page worked. It is
|
|
14
|
+
now a CSS animation on an HTML element around a static svg, which the browser can run off the
|
|
15
|
+
main thread.
|
|
16
|
+
- **A spin icon turns as a clone and inside a shadow root.** The animation is in the markup
|
|
17
|
+
rather than started by script, so `cloneNode` and list templates keep it. The spinning
|
|
18
|
+
element is a `<tosi-icon-spin>`, which adds one shared stylesheet to the document or shadow
|
|
19
|
+
root it lands in, because a document-level `@keyframes` does not reach a shadow root in
|
|
20
|
+
Chromium or Firefox. Nothing is added inside the icon, so a button holding a spinner still
|
|
21
|
+
has its caption as its `textContent`.
|
|
22
|
+
- **Spin icons hold still under `prefers-reduced-motion: reduce`**, as the live-example spinner
|
|
23
|
+
already did. Shipped in a patch on the maintainer's call: it is an accessibility default. The
|
|
24
|
+
rules are wrapped in `:where()`, so a rule of yours that names `tosi-icon-spin` overrides
|
|
25
|
+
them; for an icon inside a component's shadow root, that rule has to be in the shadow root.
|
|
26
|
+
- **The DOM inside a spin icon changed.** It was `span.tosi-icon-composite > svg`; it is now
|
|
27
|
+
`span.tosi-icon-composite > tosi-icon-spin > svg`, with a span carrying the suffix transform
|
|
28
|
+
around `<tosi-icon-spin>` when the name has `s`/`x`/`y`/`r`/`f` suffixes
|
|
29
|
+
(`spin90Loader40s_30x`). That keeps an offset icon spinning about its own centre, as before,
|
|
30
|
+
and both hold the svg's size in a taller flex container so the icon does not orbit. Speed
|
|
31
|
+
and direction are the `--tosi-icon-spin-duration` and `--tosi-icon-spin-direction` custom
|
|
32
|
+
properties on `<tosi-icon-spin>`.
|
|
33
|
+
|
|
3
34
|
## 1.16.33
|
|
4
35
|
|
|
5
36
|
Typing a link on a headset gets much shorter, and a mistyped code no longer sends you off to mint
|
package/dist/icons.js
CHANGED
|
@@ -438,6 +438,10 @@ that feed back into the resolution pipeline:
|
|
|
438
438
|
- `spin<dps><Icon>` — continuous rotation at N°/second (e.g. `spin360Loader`)
|
|
439
439
|
- `spin_<dps><Icon>` — counter-clockwise (e.g. `spin_180Star`)
|
|
440
440
|
|
|
441
|
+
The rotation is a CSS animation on an HTML wrapper, not on the `<svg>`, so the browser can
|
|
442
|
+
run it off the main thread and it keeps turning while the page is busy. Under
|
|
443
|
+
`prefers-reduced-motion: reduce` it does not turn.
|
|
444
|
+
|
|
441
445
|
The overlay rules are just string rewrites — for example, `unFoo`
|
|
442
446
|
becomes `slash25o$foo75s75o`. **Overlay icons should have a square
|
|
443
447
|
viewBox** for best results on non-square base icons.
|
|
@@ -683,27 +687,89 @@ export const svg2DataUrl = (icon, fill, stroke, strokeWidth) => {
|
|
|
683
687
|
};
|
|
684
688
|
// Appends _ to names ending in digits to prevent suffix ambiguity
|
|
685
689
|
const safeIconSuffix = (name) => /\d$/.test(name) ? name + '_' : name;
|
|
686
|
-
|
|
687
|
-
|
|
690
|
+
/*
|
|
691
|
+
The rule list and how names match it live in icon-rules.ts, shared with iconSvg. Spin
|
|
692
|
+
animates, so its renderer is installed here.
|
|
693
|
+
|
|
694
|
+
The rotation is a CSS animation on an HTML span, never on the svg (board #3508). WebKit runs
|
|
695
|
+
an animated transform on an SVG element on the main thread, whichever API starts it, so a
|
|
696
|
+
loader froze exactly while the page was busy: the one time a loader is on screen. It was also
|
|
697
|
+
started with `el.animate()`, which lives in script rather than in the markup, so a clone of
|
|
698
|
+
the icon (a list template, `cloneNode`) stood still.
|
|
699
|
+
|
|
700
|
+
The spinning element is a `<tosi-icon-spin>`, which on connecting adds one shared stylesheet
|
|
701
|
+
to whatever root it lands in. A document-level `@keyframes` does not reach an icon in a shadow
|
|
702
|
+
root in Chromium or Firefox (measured), and icons are drawn inside shadow-DOM components. A
|
|
703
|
+
`<style>` carried inside each icon did reach, but its CSS became part of the `textContent` of
|
|
704
|
+
every button holding a spinner, which is what `toHaveText` and caption matching read.
|
|
705
|
+
|
|
706
|
+
The suffix transform (`spin90Loader40s_30x`) moves to a span OUTSIDE the rotation, so the
|
|
707
|
+
icon still spins about its own centre, as it did when the rotation was appended to that
|
|
708
|
+
transform, instead of orbiting the offset. Both are `align-self: flex-start` so they stay the
|
|
709
|
+
svg's size: stretched by a taller flex container, the rotation's centre left the svg's and
|
|
710
|
+
the icon orbited.
|
|
711
|
+
|
|
712
|
+
Speed and direction are custom properties on the element, so the animation is a rule and
|
|
713
|
+
`prefers-reduced-motion` can stop it, as it stops the live-example spinner. The rules are in
|
|
714
|
+
`:where()`, so any rule of an adopter's that names `tosi-icon-spin` overrides them.
|
|
715
|
+
*/
|
|
716
|
+
const SPIN_CSS = '@keyframes tosi-icon-spin{to{transform:rotate(360deg)}}' +
|
|
717
|
+
':where(tosi-icon-spin){animation:tosi-icon-spin var(--tosi-icon-spin-duration) linear ' +
|
|
718
|
+
'infinite var(--tosi-icon-spin-direction)}' +
|
|
719
|
+
'@media (prefers-reduced-motion:reduce){:where(tosi-icon-spin){animation:none}}';
|
|
720
|
+
const spinStyled = new WeakSet();
|
|
721
|
+
let spinSheet;
|
|
722
|
+
function addSpinStyles(root) {
|
|
723
|
+
if (spinStyled.has(root))
|
|
724
|
+
return;
|
|
725
|
+
// A document or a shadow root; a detached subtree's root is neither and gets styled on
|
|
726
|
+
// connecting.
|
|
727
|
+
const isDocument = root.nodeType === Node.DOCUMENT_NODE;
|
|
728
|
+
if (!isDocument && !root.host)
|
|
729
|
+
return;
|
|
730
|
+
const target = root;
|
|
731
|
+
spinStyled.add(root);
|
|
732
|
+
try {
|
|
733
|
+
if (!spinSheet) {
|
|
734
|
+
spinSheet = new CSSStyleSheet();
|
|
735
|
+
spinSheet.replaceSync(SPIN_CSS);
|
|
736
|
+
}
|
|
737
|
+
target.adoptedStyleSheets = [...target.adoptedStyleSheets, spinSheet];
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
// No constructable stylesheets (Safari before 16.4): a <style> in the root, not the icon.
|
|
741
|
+
const style = document.createElement('style');
|
|
742
|
+
style.textContent = SPIN_CSS;
|
|
743
|
+
(isDocument ? target.head : target).append(style);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
class TosiIconSpin extends HTMLElement {
|
|
747
|
+
connectedCallback() {
|
|
748
|
+
addSpinStyles(this.getRootNode());
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (globalThis.customElements && !customElements.get('tosi-icon-spin')) {
|
|
752
|
+
customElements.define('tosi-icon-spin', TosiIconSpin);
|
|
753
|
+
}
|
|
754
|
+
const spinBox = { display: 'inline-flex', alignSelf: 'flex-start' };
|
|
688
755
|
spinRule.apply = (baseName, match, parts) => {
|
|
689
756
|
const dps = match[1].replace('_', '-');
|
|
690
757
|
const duration = 360 / Math.abs(parseFloat(dps));
|
|
691
758
|
const direction = dps.startsWith('-') ? 'reverse' : 'normal';
|
|
692
759
|
// Resolve baseName with suffixes intact — they apply to the icon
|
|
693
760
|
const icon = resolveIcon(baseName, []);
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
});
|
|
761
|
+
const { transform, transformOrigin } = icon.style;
|
|
762
|
+
icon.style.transform = '';
|
|
763
|
+
icon.style.transformOrigin = '';
|
|
764
|
+
let spun = document.createElement('tosi-icon-spin');
|
|
765
|
+
Object.assign(spun.style, spinBox);
|
|
766
|
+
spun.style.setProperty('--tosi-icon-spin-duration', `${duration}s`);
|
|
767
|
+
spun.style.setProperty('--tosi-icon-spin-direction', direction);
|
|
768
|
+
spun.append(icon);
|
|
769
|
+
if (transform) {
|
|
770
|
+
spun = elements.span({ style: { ...spinBox, transform, transformOrigin } }, spun);
|
|
705
771
|
}
|
|
706
|
-
return wrapIcon(baseName, parts,
|
|
772
|
+
return wrapIcon(baseName, parts, spun);
|
|
707
773
|
};
|
|
708
774
|
let iconIdCounter = 0;
|
|
709
775
|
function makeIcon(spec, parts) {
|