svelte-tv 1.0.5 → 1.0.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.
@@ -41,6 +41,7 @@ export interface ElementNode extends RendererNode, FocusNode {
41
41
  _transitionAnimations?: Record<string, IAnimationController | undefined>;
42
42
  _transitionAnimationVersions?: Record<string, number | undefined>;
43
43
  _lastAnyKeyPressTime?: number;
44
+ _svelteEffect?: unknown;
44
45
  _type: 'element' | 'textNode';
45
46
  _undoStyles?: string[];
46
47
  _display?: 'flex' | 'block';
@@ -15,7 +15,7 @@ export interface LightningComponent {
15
15
  export declare function getParentNode(): ElementNode | undefined;
16
16
  export declare function setParentNode(node: ElementNode): void;
17
17
  export declare function createNode(name: 'view' | 'text', props: Record<string, any>): ElementNode;
18
- export declare function mountNode(node: ElementNode, parent: ElementNode | undefined): void;
18
+ export declare function mountNode(node: ElementNode, parent: ElementNode | undefined, effect?: unknown): void;
19
19
  export declare function unmountNode(node: ElementNode): void;
20
20
  export declare function applyNodeProps(node: ElementNode, props: Record<string, any>): void;
21
21
  export declare function setTextContent(node: ElementNode, text: string | undefined): void;
@@ -0,0 +1,43 @@
1
+ import { ElementNode } from './elementNode.js';
2
+ import { TextNode } from './nodeTypes.js';
3
+ type FragmentNode = {
4
+ _type: 'fragment';
5
+ parent?: ElementNode | FragmentNode;
6
+ children: RendererNode[];
7
+ };
8
+ type CommentNode = {
9
+ _type: 'comment';
10
+ parent?: ElementNode | FragmentNode;
11
+ data: string;
12
+ };
13
+ type RendererNode = ElementNode | TextNode | FragmentNode | CommentNode;
14
+ type RendererParent = ElementNode | FragmentNode;
15
+ declare function createFragment(): FragmentNode;
16
+ declare const renderer: {
17
+ createFragment: typeof createFragment;
18
+ createElement(name: string): ElementNode;
19
+ createTextNode(data: string): TextNode;
20
+ createComment(data: string): CommentNode;
21
+ nodeType(node: RendererNode): "element" | "text" | "fragment" | "comment";
22
+ getNodeValue(node: TextNode | CommentNode): string;
23
+ getAttribute(element: ElementNode, name: string): string | null;
24
+ setAttribute(element: ElementNode, key: string, value: unknown): void;
25
+ removeAttribute(element: ElementNode, name: string): void;
26
+ hasAttribute(element: ElementNode, name: string): boolean;
27
+ setText(node: ElementNode | TextNode | CommentNode, text: string): void;
28
+ getFirstChild(node: RendererParent): RendererNode | null;
29
+ getLastChild(node: RendererParent): RendererNode | null;
30
+ getNextSibling(node: ElementNode | TextNode | CommentNode): RendererNode | null;
31
+ insert(parent: RendererParent, node: RendererNode, anchor: RendererNode | null): void;
32
+ remove(node: ElementNode | TextNode | CommentNode): void;
33
+ getParent(node: ElementNode | TextNode | CommentNode): RendererParent | null;
34
+ addEventListener(target: ElementNode, type: string, handler: any): void;
35
+ removeEventListener(target: ElementNode, type: string, handler: any): void;
36
+ foreign: {
37
+ insertIntoForeign(_parent: Node, node: RendererNode, _anchor: ChildNode | null): void;
38
+ insertForeign(_parent: RendererParent, _node: Node, _anchor: RendererNode | null): void;
39
+ removeForeign(node: ChildNode): void;
40
+ removeFromForeign(node: RendererNode): void;
41
+ };
42
+ };
43
+ export default renderer;
@@ -0,0 +1,9 @@
1
+ import type { IconifyIconOnLoad, IconifyIconProps } from '@iconify/svelte/dist/functions';
2
+ import { type NodeProps } from '../index.js';
3
+ type Props = Omit<NodeProps, 'color' | 'height' | 'width'> & IconifyIconProps & {
4
+ onLoad?: IconifyIconOnLoad;
5
+ onload?: IconifyIconOnLoad;
6
+ };
7
+ declare const Icon: import("svelte").Component<Props, {}, "">;
8
+ type Icon = ReturnType<typeof Icon>;
9
+ export default Icon;
@@ -5,6 +5,7 @@ export { default as Drawer } from './Drawer.svelte';
5
5
  export { default as FadeInOut } from './FadeInOut.svelte';
6
6
  export { default as FPSCounter } from './FPSCounter.svelte';
7
7
  export { default as Grid } from './Grid.svelte';
8
+ export { default as Icon } from './Icon.svelte';
8
9
  export { default as IconButton } from './IconButton.svelte';
9
10
  export { default as Image } from './Image.svelte';
10
11
  export { default as Marquee } from './Marquee.svelte';
@@ -2,7 +2,7 @@ export { default as HashRouter } from './HashRouter.svelte';
2
2
  export { default as KeepAliveRoute } from './KeepAliveRoute.svelte';
3
3
  export { default as Navigate } from './Navigate.svelte';
4
4
  export { default as Route } from './Route.svelte';
5
- export { location, navigate, params, route, routeData, } from './context.js';
5
+ export { location, navigate, params, route, routeData } from './context.js';
6
6
  export { lazy } from './lazy.js';
7
7
  export { createLocation, matchRoutes, normalizePath } from './match.js';
8
8
  export type { LazyRouteComponent, MatchedRoute, NavigateFn, NavigateOptions, RouteComponent, RouteComponentProps, RouteDefinition, RouteLocation, RouteParams, RoutePreload, RoutePreloadArgs, RouteRenderable, } from './types.js';
@@ -41,6 +41,7 @@ export interface ElementNode extends RendererNode, FocusNode {
41
41
  _transitionAnimations?: Record<string, IAnimationController | undefined>;
42
42
  _transitionAnimationVersions?: Record<string, number | undefined>;
43
43
  _lastAnyKeyPressTime?: number;
44
+ _svelteEffect?: unknown;
44
45
  _type: 'element' | 'textNode';
45
46
  _undoStyles?: string[];
46
47
  _display?: 'flex' | 'block';
@@ -406,6 +406,7 @@ export class ElementNode {
406
406
  this._transitionAnimations = undefined;
407
407
  this._transitionAnimationVersions = undefined;
408
408
  this._lastAnyKeyPressTime = undefined;
409
+ this._svelteEffect = undefined;
409
410
  this._undoStyles = undefined;
410
411
  this._display = undefined;
411
412
  this._onLayout = undefined;
@@ -886,6 +887,9 @@ export class ElementNode {
886
887
  * A truthy value enables autofocus, otherwise disables it.
887
888
  */
888
889
  set autofocus(val) {
890
+ if (val === this._autofocus) {
891
+ return;
892
+ }
889
893
  this._autofocus = val;
890
894
  // Defer setFocus so children render first (forwardFocus needs them).
891
895
  // The post-mutation focus phase calls setFocus on this element.
@@ -893,6 +897,9 @@ export class ElementNode {
893
897
  deferredFocusElement = this;
894
898
  schedulePostMutation();
895
899
  }
900
+ else if (deferredFocusElement === this) {
901
+ deferredFocusElement = null;
902
+ }
896
903
  }
897
904
  get autofocus() {
898
905
  return this._autofocus;
@@ -39,24 +39,24 @@ const addFocusDebug = (prevFocusPath, newFocusPath) => {
39
39
  if (needFocusDebugStyles) {
40
40
  const style = document.createElement('style');
41
41
  style.type = 'text/css';
42
- style.innerHTML = `
43
- [data-focus="3"] {
44
- border: 2px solid rgba(255, 33, 33, 0.2);
45
- border-radius: 5px;
46
- transition: border-color 0.3s ease;
47
- }
48
-
49
- [data-focus="2"] {
50
- border: 2px solid rgba(255, 33, 33, 0.4);
51
- border-radius: 5px;
52
- transition: border-color 0.3s ease;
53
- }
54
-
55
- [data-focus="1"] {
56
- border: 4px solid rgba(255, 33, 33, 0.9);
57
- border-radius: 5px;
58
- transition: border-color 0.5s ease;
59
- }
42
+ style.innerHTML = `
43
+ [data-focus="3"] {
44
+ border: 2px solid rgba(255, 33, 33, 0.2);
45
+ border-radius: 5px;
46
+ transition: border-color 0.3s ease;
47
+ }
48
+
49
+ [data-focus="2"] {
50
+ border: 2px solid rgba(255, 33, 33, 0.4);
51
+ border-radius: 5px;
52
+ transition: border-color 0.3s ease;
53
+ }
54
+
55
+ [data-focus="1"] {
56
+ border: 4px solid rgba(255, 33, 33, 0.9);
57
+ border-radius: 5px;
58
+ transition: border-color 0.5s ease;
59
+ }
60
60
  `;
61
61
  document.head.appendChild(style);
62
62
  needFocusDebugStyles = false;
@@ -1,5 +1,5 @@
1
1
  import * as lng from '@lightningjs/renderer';
2
- import { CanvasRenderer, CanvasTextRenderer } from '@lightningjs/renderer/canvas';
2
+ import { CanvasRenderer, CanvasTextRenderer, } from '@lightningjs/renderer/canvas';
3
3
  import { registerDefaultShaders } from './shaders.js';
4
4
  export let renderer;
5
5
  export const getRenderer = () => renderer;
@@ -15,7 +15,7 @@ export interface LightningComponent {
15
15
  export declare function getParentNode(): ElementNode | undefined;
16
16
  export declare function setParentNode(node: ElementNode): void;
17
17
  export declare function createNode(name: 'view' | 'text', props: Record<string, any>): ElementNode;
18
- export declare function mountNode(node: ElementNode, parent: ElementNode | undefined): void;
18
+ export declare function mountNode(node: ElementNode, parent: ElementNode | undefined, effect?: unknown): void;
19
19
  export declare function unmountNode(node: ElementNode): void;
20
20
  export declare function applyNodeProps(node: ElementNode, props: Record<string, any>): void;
21
21
  export declare function setTextContent(node: ElementNode, text: string | undefined): void;
@@ -1,5 +1,6 @@
1
1
  import { getContext, setContext } from 'svelte';
2
- import { ElementNode, enqueueDelete, } from './elementNode.js';
2
+ import { active_effect } from 'svelte/internal/client';
3
+ import { ElementNode, enqueueDelete } from './elementNode.js';
3
4
  import { TextNode } from './nodeTypes.js';
4
5
  import { isElementText } from './utils.js';
5
6
  const nodeContext = Symbol('svelte-tv-node');
@@ -14,10 +15,11 @@ export function createNode(name, props) {
14
15
  applyNodeProps(node, props);
15
16
  return node;
16
17
  }
17
- export function mountNode(node, parent) {
18
+ export function mountNode(node, parent, effect = active_effect) {
18
19
  if (!parent)
19
20
  return;
20
- parent.insertChild(node);
21
+ node._svelteEffect = effect;
22
+ parent.insertChild(node, findNextSibling(node, parent));
21
23
  if (parent.rendered) {
22
24
  node.render(true);
23
25
  }
@@ -54,6 +56,56 @@ export function applyNodeProps(node, props) {
54
56
  }
55
57
  node.rerender();
56
58
  }
59
+ function findNextSibling(node, parent) {
60
+ const effect = node._svelteEffect;
61
+ if (!isSvelteEffect(effect))
62
+ return undefined;
63
+ for (const child of parent.children) {
64
+ if (isSvelteEffect(child._svelteEffect) &&
65
+ isEffectBefore(effect, child._svelteEffect)) {
66
+ return child;
67
+ }
68
+ }
69
+ }
70
+ function isEffectBefore(a, b) {
71
+ if (a === b)
72
+ return false;
73
+ const aPath = effectPath(a);
74
+ const bPath = effectPath(b);
75
+ let index = 0;
76
+ while (aPath[index] && aPath[index] === bPath[index]) {
77
+ index++;
78
+ }
79
+ const aChild = aPath[index];
80
+ const bChild = bPath[index];
81
+ if (!aChild || !bChild)
82
+ return false;
83
+ let sibling = bChild.prev;
84
+ while (sibling) {
85
+ if (sibling === aChild)
86
+ return true;
87
+ sibling = sibling.prev;
88
+ }
89
+ return false;
90
+ }
91
+ function effectPath(effect) {
92
+ const path = [];
93
+ let current = effect;
94
+ while (current) {
95
+ path.unshift(current);
96
+ current = current.parent;
97
+ }
98
+ return path;
99
+ }
100
+ function isSvelteEffect(value) {
101
+ if (!value || typeof value !== 'object')
102
+ return false;
103
+ const effect = value;
104
+ return ('parent' in effect &&
105
+ (effect.parent === null || typeof effect.parent === 'object') &&
106
+ 'prev' in effect &&
107
+ (effect.prev === null || typeof effect.prev === 'object'));
108
+ }
57
109
  export function setTextContent(node, text) {
58
110
  if (!isElementText(node))
59
111
  return;
@@ -0,0 +1,43 @@
1
+ import { ElementNode } from './elementNode.js';
2
+ import { TextNode } from './nodeTypes.js';
3
+ type FragmentNode = {
4
+ _type: 'fragment';
5
+ parent?: ElementNode | FragmentNode;
6
+ children: RendererNode[];
7
+ };
8
+ type CommentNode = {
9
+ _type: 'comment';
10
+ parent?: ElementNode | FragmentNode;
11
+ data: string;
12
+ };
13
+ type RendererNode = ElementNode | TextNode | FragmentNode | CommentNode;
14
+ type RendererParent = ElementNode | FragmentNode;
15
+ declare function createFragment(): FragmentNode;
16
+ declare const renderer: {
17
+ createFragment: typeof createFragment;
18
+ createElement(name: string): ElementNode;
19
+ createTextNode(data: string): TextNode;
20
+ createComment(data: string): CommentNode;
21
+ nodeType(node: RendererNode): "element" | "text" | "fragment" | "comment";
22
+ getNodeValue(node: TextNode | CommentNode): string;
23
+ getAttribute(element: ElementNode, name: string): string | null;
24
+ setAttribute(element: ElementNode, key: string, value: unknown): void;
25
+ removeAttribute(element: ElementNode, name: string): void;
26
+ hasAttribute(element: ElementNode, name: string): boolean;
27
+ setText(node: ElementNode | TextNode | CommentNode, text: string): void;
28
+ getFirstChild(node: RendererParent): RendererNode | null;
29
+ getLastChild(node: RendererParent): RendererNode | null;
30
+ getNextSibling(node: ElementNode | TextNode | CommentNode): RendererNode | null;
31
+ insert(parent: RendererParent, node: RendererNode, anchor: RendererNode | null): void;
32
+ remove(node: ElementNode | TextNode | CommentNode): void;
33
+ getParent(node: ElementNode | TextNode | CommentNode): RendererParent | null;
34
+ addEventListener(target: ElementNode, type: string, handler: any): void;
35
+ removeEventListener(target: ElementNode, type: string, handler: any): void;
36
+ foreign: {
37
+ insertIntoForeign(_parent: Node, node: RendererNode, _anchor: ChildNode | null): void;
38
+ insertForeign(_parent: RendererParent, _node: Node, _anchor: RendererNode | null): void;
39
+ removeForeign(node: ChildNode): void;
40
+ removeFromForeign(node: RendererNode): void;
41
+ };
42
+ };
43
+ export default renderer;
@@ -0,0 +1,178 @@
1
+ import { createRenderer } from 'svelte/renderer';
2
+ import { ElementNode, enqueueDelete } from './elementNode.js';
3
+ import { TextNode } from './nodeTypes.js';
4
+ import { rootNode } from './root.js';
5
+ import { applyNodeProps, setTextContent } from './svelteNode.js';
6
+ import { isElementText } from './utils.js';
7
+ function createFragment() {
8
+ return {
9
+ _type: 'fragment',
10
+ children: [],
11
+ };
12
+ }
13
+ function getChildren(node) {
14
+ return node.children;
15
+ }
16
+ function getRendererParent(node) {
17
+ const parent = node.parent;
18
+ return parent;
19
+ }
20
+ function detach(node) {
21
+ const parent = getRendererParent(node);
22
+ if (!parent)
23
+ return;
24
+ const children = getChildren(parent);
25
+ const index = children.indexOf(node);
26
+ if (index !== -1) {
27
+ children.splice(index, 1);
28
+ }
29
+ node.parent = undefined;
30
+ }
31
+ function removeRendererNode(node) {
32
+ if (node._type === 'fragment') {
33
+ for (const child of [...node.children]) {
34
+ removeRendererNode(child);
35
+ }
36
+ return;
37
+ }
38
+ renderer.remove(node);
39
+ }
40
+ function renderInserted(node, parent) {
41
+ if (!(node instanceof ElementNode) || !(parent instanceof ElementNode))
42
+ return;
43
+ if (parent.rendered) {
44
+ node.render(true);
45
+ }
46
+ }
47
+ function insertChild(parent, node, anchor) {
48
+ detach(node);
49
+ node.parent = parent;
50
+ const children = getChildren(parent);
51
+ const index = anchor ? children.indexOf(anchor) : -1;
52
+ children.splice(index === -1 ? children.length : index, 0, node);
53
+ renderInserted(node, parent);
54
+ }
55
+ function setRef(element, ref) {
56
+ const previous = element._svelteRef;
57
+ if (previous === ref)
58
+ return;
59
+ previous?.(undefined);
60
+ element._svelteRef = ref;
61
+ ref?.(element);
62
+ }
63
+ const renderer = createRenderer({
64
+ createFragment,
65
+ createElement(name) {
66
+ return new ElementNode(name === 'text' ? 'text' : 'view');
67
+ },
68
+ createTextNode(data) {
69
+ return new TextNode(data);
70
+ },
71
+ createComment(data) {
72
+ return {
73
+ _type: 'comment',
74
+ data,
75
+ };
76
+ },
77
+ nodeType(node) {
78
+ if (node instanceof ElementNode)
79
+ return 'element';
80
+ if (node instanceof TextNode)
81
+ return 'text';
82
+ return node._type;
83
+ },
84
+ getNodeValue(node) {
85
+ return node instanceof TextNode ? node.text : node.data;
86
+ },
87
+ getAttribute(element, name) {
88
+ const value = element[name];
89
+ return value == null ? null : String(value);
90
+ },
91
+ setAttribute(element, key, value) {
92
+ if (key === 'ref') {
93
+ setRef(element, value);
94
+ return;
95
+ }
96
+ applyNodeProps(element, { [key]: value });
97
+ },
98
+ removeAttribute(element, name) {
99
+ if (name === 'ref') {
100
+ setRef(element, undefined);
101
+ return;
102
+ }
103
+ applyNodeProps(element, { [name]: undefined });
104
+ },
105
+ hasAttribute(element, name) {
106
+ return element[name] !== undefined;
107
+ },
108
+ setText(node, text) {
109
+ if (node instanceof TextNode) {
110
+ node.text = text;
111
+ }
112
+ else if (node instanceof ElementNode && isElementText(node)) {
113
+ setTextContent(node, text);
114
+ }
115
+ else if (!(node instanceof ElementNode)) {
116
+ node.data = text;
117
+ }
118
+ },
119
+ getFirstChild(node) {
120
+ return getChildren(node)[0] ?? null;
121
+ },
122
+ getLastChild(node) {
123
+ return getChildren(node).at(-1) ?? null;
124
+ },
125
+ getNextSibling(node) {
126
+ const parent = getRendererParent(node);
127
+ if (!parent)
128
+ return null;
129
+ const children = getChildren(parent);
130
+ const index = children.indexOf(node);
131
+ return index === -1 ? null : (children[index + 1] ?? null);
132
+ },
133
+ insert(parent, node, anchor) {
134
+ if (node._type === 'fragment') {
135
+ for (const child of [...node.children]) {
136
+ insertChild(parent, child, anchor);
137
+ }
138
+ return;
139
+ }
140
+ insertChild(parent, node, anchor);
141
+ },
142
+ remove(node) {
143
+ detach(node);
144
+ if (node instanceof ElementNode) {
145
+ setRef(node, undefined);
146
+ enqueueDelete(node, -1);
147
+ }
148
+ },
149
+ getParent(node) {
150
+ return getRendererParent(node) ?? null;
151
+ },
152
+ addEventListener(target, type, handler) {
153
+ target.onEvent = {
154
+ ...target.onEvent,
155
+ [type]: handler,
156
+ };
157
+ },
158
+ removeEventListener(target, type, handler) {
159
+ const onEvent = target.onEvent;
160
+ if (onEvent && onEvent[type] === handler) {
161
+ delete onEvent[type];
162
+ target.onEvent = onEvent;
163
+ }
164
+ },
165
+ foreign: {
166
+ insertIntoForeign(_parent, node, _anchor) {
167
+ renderer.insert(rootNode, node, null);
168
+ },
169
+ insertForeign(_parent, _node, _anchor) { },
170
+ removeForeign(node) {
171
+ node.remove();
172
+ },
173
+ removeFromForeign(node) {
174
+ removeRendererNode(node);
175
+ },
176
+ },
177
+ });
178
+ export default renderer;
@@ -80,17 +80,17 @@ export function logRenderTree(node) {
80
80
  parent = parent.parent;
81
81
  }
82
82
  tree.reverse();
83
- let output = `
84
- function convertEffectsToShader(styleEffects) {
85
- const effects = [];
86
- let index = 0;
87
-
88
- for (const [type, props] of Object.entries(styleEffects)) {
89
- effects.push({ type, props });
90
- index++;
91
- }
92
- return createShader('DynamicShader', { effects });
93
- }
83
+ let output = `
84
+ function convertEffectsToShader(styleEffects) {
85
+ const effects = [];
86
+ let index = 0;
87
+
88
+ for (const [type, props] of Object.entries(styleEffects)) {
89
+ effects.push({ type, props });
90
+ index++;
91
+ }
92
+ return createShader('DynamicShader', { effects });
93
+ }
94
94
  `;
95
95
  tree.forEach((node, i) => {
96
96
  if (!node._rendererProps) {
@@ -103,11 +103,11 @@ function convertEffectsToShader(styleEffects) {
103
103
  ? `props${i}.shader = convertEffectsToShader(${JSON.stringify(node._effects, null, 2)});`
104
104
  : '';
105
105
  const parent = i === 0 ? 'rootNode' : `node${i - 1}`;
106
- output += `
107
- const props${i} = ${props};
108
- props${i}.parent = ${parent};
109
- ${effects}
110
- const node${i} = renderer.createNode(props${i});
106
+ output += `
107
+ const props${i} = ${props};
108
+ props${i}.parent = ${parent};
109
+ ${effects}
110
+ const node${i} = renderer.createNode(props${i});
111
111
  `;
112
112
  });
113
113
  return output;
@@ -98,7 +98,7 @@ const nodeProps = $derived.by(() => {
98
98
  onFocus={chainFunctions(props.onFocus, onFocus)}
99
99
  strictBounds={false}
100
100
  y={props.scroll === 'none'
101
- ? props.y ?? 0
101
+ ? (props.y ?? 0)
102
102
  : -Math.floor(focusedIndex / columns()) * totalHeight() + (props.y || 0)}
103
103
  >
104
104
  {#each props.items as item, index}
@@ -0,0 +1,108 @@
1
+ <script lang="ts">import { addIcon, generateIcon, getIcon, loadIcon } from "@iconify/svelte/dist/functions";
2
+ import { renderer } from "../index.js";
3
+ import View from "../View.svelte";
4
+ let props = $props();
5
+ let texture = $state();
6
+ let viewWidth = $state();
7
+ let viewHeight = $state();
8
+ let loadId = 0;
9
+ const storagePrefix = "svelte-tv:iconify:v1:";
10
+ function numberSize(size) {
11
+ if (typeof size === "number") return size;
12
+ if (typeof size !== "string") return undefined;
13
+ const match = size.match(/^(\d+(?:\.\d+)?)(px)?$/);
14
+ return match ? Number(match[1]) : undefined;
15
+ }
16
+ function base64Encode(value) {
17
+ const encoded = encodeURIComponent(value);
18
+ let binary = "";
19
+ for (let i = 0; i < encoded.length; i++) {
20
+ if (encoded[i] === "%") {
21
+ binary += String.fromCharCode(parseInt(encoded.slice(i + 1, i + 3), 16));
22
+ i += 2;
23
+ } else {
24
+ binary += encoded[i];
25
+ }
26
+ }
27
+ return btoa(binary);
28
+ }
29
+ function createSvg(icon) {
30
+ const data = generateIcon(icon, {
31
+ ...props,
32
+ mode: "svg"
33
+ });
34
+ if (!data?.svg) return undefined;
35
+ const width = numberSize(data.attributes.width) ?? icon.width ?? 16;
36
+ const height = numberSize(data.attributes.height) ?? icon.height ?? 16;
37
+ const attributes = Object.entries(data.attributes).map(([key, value]) => `${key}="${String(value).replace(/"/g, "&quot;")}"`).join(" ");
38
+ viewWidth = typeof props.w === "number" ? props.w : width;
39
+ viewHeight = typeof props.h === "number" ? props.h : height;
40
+ return `<svg ${attributes}>${data.body}</svg>`;
41
+ }
42
+ function createTexture(icon) {
43
+ const svg = createSvg(icon);
44
+ if (!svg) return undefined;
45
+ return renderer.createTexture("ImageTexture", {
46
+ src: `data:image/svg+xml;base64,${base64Encode(svg)}`,
47
+ type: "svg",
48
+ w: viewWidth,
49
+ h: viewHeight
50
+ });
51
+ }
52
+ function readStoredIcon(name) {
53
+ if (typeof localStorage === "undefined") return undefined;
54
+ try {
55
+ const raw = localStorage.getItem(storagePrefix + name);
56
+ if (!raw) return undefined;
57
+ const icon = JSON.parse(raw);
58
+ if (icon && typeof icon.body === "string") {
59
+ addIcon(name, icon);
60
+ return getIcon(name) ?? icon;
61
+ }
62
+ } catch {}
63
+ }
64
+ function storeIcon(name, icon) {
65
+ if (typeof localStorage === "undefined") return;
66
+ try {
67
+ localStorage.setItem(storagePrefix + name, JSON.stringify(icon));
68
+ } catch {}
69
+ }
70
+ $effect(() => {
71
+ const currentLoadId = ++loadId;
72
+ texture = undefined;
73
+ if (typeof props.icon !== "string") {
74
+ texture = createTexture(props.icon);
75
+ return;
76
+ }
77
+ const cachedIcon = getIcon(props.icon);
78
+ const storedIcon = cachedIcon === undefined ? readStoredIcon(props.icon) : cachedIcon;
79
+ if (storedIcon) {
80
+ texture = createTexture(storedIcon);
81
+ props.onLoad?.(props.icon);
82
+ props.onload?.(props.icon);
83
+ return;
84
+ }
85
+ if (storedIcon === null) return;
86
+ loadIcon(props.icon).then((icon) => {
87
+ if (currentLoadId !== loadId) return;
88
+ storeIcon(props.icon, icon);
89
+ texture = createTexture(icon);
90
+ props.onLoad?.(props.icon);
91
+ props.onload?.(props.icon);
92
+ }).catch(() => {});
93
+ });
94
+ const nodeProps = $derived.by(() => {
95
+ const { icon, mode, color, flip, hFlip, vFlip, rotate, inline, width, height, id, onLoad, onload, ...rest } = props;
96
+ return rest;
97
+ });
98
+ </script>
99
+
100
+ {#if texture}
101
+ <View
102
+ {...nodeProps}
103
+ {texture}
104
+ w={viewWidth}
105
+ h={viewHeight}
106
+ color={0xffffffff}
107
+ />
108
+ {/if}
@@ -0,0 +1,9 @@
1
+ import type { IconifyIconOnLoad, IconifyIconProps } from '@iconify/svelte/dist/functions';
2
+ import { type NodeProps } from '../index.js';
3
+ type Props = Omit<NodeProps, 'color' | 'height' | 'width'> & IconifyIconProps & {
4
+ onLoad?: IconifyIconOnLoad;
5
+ onload?: IconifyIconOnLoad;
6
+ };
7
+ declare const Icon: import("svelte").Component<Props, {}, "">;
8
+ type Icon = ReturnType<typeof Icon>;
9
+ export default Icon;
@@ -34,6 +34,11 @@ const nodeProps = $derived.by(() => {
34
34
  });
35
35
  </script>
36
36
 
37
- <View {...nodeProps} src={currentSrc} color={props.color || 0xffffffff} {texture}>
37
+ <View
38
+ {...nodeProps}
39
+ src={currentSrc}
40
+ color={props.color || 0xffffffff}
41
+ {texture}
42
+ >
38
43
  {@render props.children?.()}
39
44
  </View>
@@ -57,6 +57,11 @@ const textNodeProps = $derived.by(() => {
57
57
  <View {...nodeProps} clipping={props.marquee}>
58
58
  <Text {...textNodeProps} bind:this={textNode} text={props.text} />
59
59
  {#if props.marquee}
60
- <Text {...textNodeProps} bind:this={repeatNode} x={repeatX} text={props.text} />
60
+ <Text
61
+ {...textNodeProps}
62
+ bind:this={repeatNode}
63
+ x={repeatX}
64
+ text={props.text}
65
+ />
61
66
  {/if}
62
67
  </View>
@@ -5,6 +5,7 @@ export { default as Drawer } from './Drawer.svelte';
5
5
  export { default as FadeInOut } from './FadeInOut.svelte';
6
6
  export { default as FPSCounter } from './FPSCounter.svelte';
7
7
  export { default as Grid } from './Grid.svelte';
8
+ export { default as Icon } from './Icon.svelte';
8
9
  export { default as IconButton } from './IconButton.svelte';
9
10
  export { default as Image } from './Image.svelte';
10
11
  export { default as Marquee } from './Marquee.svelte';
@@ -5,6 +5,7 @@ export { default as Drawer } from './Drawer.svelte';
5
5
  export { default as FadeInOut } from './FadeInOut.svelte';
6
6
  export { default as FPSCounter } from './FPSCounter.svelte';
7
7
  export { default as Grid } from './Grid.svelte';
8
+ export { default as Icon } from './Icon.svelte';
8
9
  export { default as IconButton } from './IconButton.svelte';
9
10
  export { default as Image } from './Image.svelte';
10
11
  export { default as Marquee } from './Marquee.svelte';
@@ -2,7 +2,7 @@ export { default as HashRouter } from './HashRouter.svelte';
2
2
  export { default as KeepAliveRoute } from './KeepAliveRoute.svelte';
3
3
  export { default as Navigate } from './Navigate.svelte';
4
4
  export { default as Route } from './Route.svelte';
5
- export { location, navigate, params, route, routeData, } from './context.js';
5
+ export { location, navigate, params, route, routeData } from './context.js';
6
6
  export { lazy } from './lazy.js';
7
7
  export { createLocation, matchRoutes, normalizePath } from './match.js';
8
8
  export type { LazyRouteComponent, MatchedRoute, NavigateFn, NavigateOptions, RouteComponent, RouteComponentProps, RouteDefinition, RouteLocation, RouteParams, RoutePreload, RoutePreloadArgs, RouteRenderable, } from './types.js';
@@ -2,6 +2,6 @@ export { default as HashRouter } from './HashRouter.svelte';
2
2
  export { default as KeepAliveRoute } from './KeepAliveRoute.svelte';
3
3
  export { default as Navigate } from './Navigate.svelte';
4
4
  export { default as Route } from './Route.svelte';
5
- export { location, navigate, params, route, routeData, } from './context.js';
5
+ export { location, navigate, params, route, routeData } from './context.js';
6
6
  export { lazy } from './lazy.js';
7
7
  export { createLocation, matchRoutes, normalizePath } from './match.js';
@@ -43,10 +43,7 @@ function matchChildren(routes, segments, index, params, basePath, chain) {
43
43
  if (!result)
44
44
  continue;
45
45
  const fullPath = joinPaths(basePath, route.path);
46
- const nextChain = [
47
- ...chain,
48
- { route, params: result.params, fullPath },
49
- ];
46
+ const nextChain = [...chain, { route, params: result.params, fullPath }];
50
47
  const nextIndex = result.index;
51
48
  const score = result.score;
52
49
  if (route.children.length > 0) {
@@ -0,0 +1,3 @@
1
+ declare module 'svelte/internal/client' {
2
+ export const active_effect: unknown;
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-tv",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Svelte speed for every screen 🚀✨",
5
5
  "type": "module",
6
6
  "exports": {
@@ -66,6 +66,7 @@
66
66
  "svelte": "^5.56.4"
67
67
  },
68
68
  "dependencies": {
69
+ "@iconify/svelte": "^5.2.2",
69
70
  "@lightningjs/msdf-generator": "^1.3.0",
70
71
  "@lightningjs/renderer": "^3.0.6",
71
72
  "esm-env": "^1.2.2"