@lightningtv/solid 3.0.1 → 3.0.3

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.
Files changed (33) hide show
  1. package/dist/src/core/elementNode.d.ts +3 -0
  2. package/dist/src/core/elementNode.js +25 -37
  3. package/dist/src/core/elementNode.js.map +1 -1
  4. package/dist/src/core/intrinsicTypes.d.ts +1 -1
  5. package/dist/src/index.d.ts +2 -2
  6. package/dist/src/index.js +1 -2
  7. package/dist/src/index.js.map +1 -1
  8. package/dist/src/primitives/FadeInOut.d.ts +9 -1
  9. package/dist/src/primitives/FadeInOut.jsx +14 -2
  10. package/dist/src/primitives/FadeInOut.jsx.map +1 -1
  11. package/dist/src/primitives/Lazy.jsx +1 -1
  12. package/dist/src/primitives/Lazy.jsx.map +1 -1
  13. package/dist/src/primitives/Virtual.jsx +11 -3
  14. package/dist/src/primitives/Virtual.jsx.map +1 -1
  15. package/dist/src/primitives/borderBox.d.ts +8 -0
  16. package/dist/src/primitives/borderBox.jsx +46 -0
  17. package/dist/src/primitives/borderBox.jsx.map +1 -0
  18. package/dist/src/primitives/index.d.ts +2 -1
  19. package/dist/src/primitives/index.js +2 -1
  20. package/dist/src/primitives/index.js.map +1 -1
  21. package/dist/src/primitives/utils/handleNavigation.js +4 -2
  22. package/dist/src/primitives/utils/handleNavigation.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/src/core/elementNode.ts +50 -115
  26. package/src/core/intrinsicTypes.ts +5 -25
  27. package/src/index.ts +2 -11
  28. package/src/primitives/FadeInOut.tsx +17 -4
  29. package/src/primitives/Lazy.tsx +1 -1
  30. package/src/primitives/Virtual.tsx +14 -4
  31. package/src/primitives/borderBox.tsx +60 -0
  32. package/src/primitives/index.ts +2 -5
  33. package/src/primitives/utils/handleNavigation.ts +14 -39
@@ -49,11 +49,7 @@ import type {
49
49
  } from '@lightningjs/renderer';
50
50
  import { assertTruthy } from '@lightningjs/renderer/utils';
51
51
  import { NodeType } from './nodeTypes.js';
52
- import {
53
- ForwardFocusHandler,
54
- setActiveElement,
55
- FocusNode,
56
- } from './focusManager.js';
52
+ import { ForwardFocusHandler, setActiveElement, FocusNode } from './focusManager.js';
57
53
  import simpleAnimation, { SimpleAnimationSettings } from './animation.js';
58
54
 
59
55
  let layoutRunQueued = false;
@@ -77,11 +73,7 @@ function runLayout() {
77
73
  }
78
74
  }
79
75
 
80
- const parseAndAssignShaderProps = (
81
- prefix: string,
82
- obj: Record<string, any>,
83
- props: Record<string, any> = {},
84
- ) => {
76
+ const parseAndAssignShaderProps = (prefix: string, obj: Record<string, any>, props: Record<string, any> = {}) => {
85
77
  if (!obj) return;
86
78
  props[prefix] = obj;
87
79
  Object.entries(obj).forEach(([key, value]) => {
@@ -144,7 +136,6 @@ const LightningRendererNonAnimatingProps = [
144
136
  'contain',
145
137
  'data',
146
138
  'destroyed',
147
- 'fontFamily',
148
139
  'fontStretch',
149
140
  'fontStyle',
150
141
  'imageType',
@@ -181,12 +172,7 @@ declare global {
181
172
  }
182
173
 
183
174
  export type RendererNode = AddColorString<
184
- Partial<
185
- NewOmit<
186
- INode,
187
- 'parent' | 'shader' | 'src' | 'children' | 'id' | 'removeChild'
188
- >
189
- >
175
+ Partial<NewOmit<INode, 'parent' | 'shader' | 'src' | 'children' | 'id' | 'removeChild'>>
190
176
  >;
191
177
  export interface ElementNode extends RendererNode, FocusNode {
192
178
  [key: string]: unknown;
@@ -208,6 +194,7 @@ export interface ElementNode extends RendererNode, FocusNode {
208
194
  _containsFlexGrow?: boolean | null;
209
195
  _hasRenderedChildren?: boolean;
210
196
  _effects?: StyleEffects;
197
+ _fontFamily?: string;
211
198
  _id: string | undefined;
212
199
  _parent: ElementNode | undefined;
213
200
  _rendererProps?: any;
@@ -270,10 +257,7 @@ export interface ElementNode extends RendererNode, FocusNode {
270
257
  /**
271
258
  * The underlying Lightning Renderer node object. This is where the properties are ultimately set for rendering.
272
259
  */
273
- lng:
274
- | Partial<ElementNode>
275
- | IRendererNode
276
- | (IRendererTextNode & { shader?: any });
260
+ lng: Partial<ElementNode> | IRendererNode | (IRendererTextNode & { shader?: any });
277
261
  /**
278
262
  * A reference to the `ElementNode` instance. Can be an object or a callback function.
279
263
  */
@@ -429,13 +413,7 @@ export interface ElementNode extends RendererNode, FocusNode {
429
413
  *
430
414
  * @see @see https://lightning-tv.github.io/solid/#/flow/layout?id=flex
431
415
  */
432
- justifyContent?:
433
- | 'flexStart'
434
- | 'flexEnd'
435
- | 'center'
436
- | 'spaceBetween'
437
- | 'spaceAround'
438
- | 'spaceEvenly';
416
+ justifyContent?: 'flexStart' | 'flexEnd' | 'center' | 'spaceBetween' | 'spaceAround' | 'spaceEvenly';
439
417
  /**
440
418
  * Applies a linear gradient effect to the element.
441
419
  *
@@ -519,10 +497,7 @@ export interface ElementNode extends RendererNode, FocusNode {
519
497
  *
520
498
  * @see https://lightning-tv.github.io/solid/#/essentials/transitions?id=transitions-animations
521
499
  */
522
- transition?:
523
- | Record<string, AnimationSettings | undefined | true | false>
524
- | true
525
- | false;
500
+ transition?: Record<string, AnimationSettings | undefined | true | false> | true | false;
526
501
  /**
527
502
  * Optional handlers for animation events.
528
503
  *
@@ -670,23 +645,29 @@ export class ElementNode extends Object {
670
645
  }
671
646
 
672
647
  set fontWeight(v) {
648
+ if (this._fontWeight === v) {
649
+ return;
650
+ }
651
+
673
652
  this._fontWeight = v;
674
- const family = this.fontFamily || Config.fontSettings?.fontFamily;
675
- const weight =
676
- (Config.fontWeightAlias &&
677
- (Config.fontWeightAlias[v as string] as number | string)) ??
678
- v;
679
- this.fontFamily = `${family}${weight}`;
653
+ const weight = (Config.fontWeightAlias && (Config.fontWeightAlias[v as string] as number | string)) ?? v;
654
+ (this.lng as any).fontFamily = `${this.fontFamily}${weight}`;
680
655
  }
681
656
 
682
657
  get fontWeight() {
683
658
  return this._fontWeight;
684
659
  }
685
660
 
686
- insertChild(
687
- node: ElementNode | ElementText | TextNode,
688
- beforeNode?: ElementNode | ElementText | TextNode | null,
689
- ) {
661
+ set fontFamily(v) {
662
+ this._fontFamily = v;
663
+ (this.lng as any).fontFamily = v;
664
+ }
665
+
666
+ get fontFamily() {
667
+ return this._fontFamily || Config.fontSettings?.fontFamily;
668
+ }
669
+
670
+ insertChild(node: ElementNode | ElementText | TextNode, beforeNode?: ElementNode | ElementText | TextNode | null) {
690
671
  // always remove nodes if they have a parent - for back swap of node
691
672
  // this will then put the node at the end of the array when re-added
692
673
  if (node.parent) {
@@ -735,12 +716,8 @@ export class ElementNode extends Object {
735
716
  return undefined;
736
717
  }
737
718
 
738
- set shader(
739
- shaderProps: IRendererShader | [kind: string, props: IRendererShaderProps],
740
- ) {
741
- this.lng.shader = isArray(shaderProps)
742
- ? renderer.createShader(...shaderProps)
743
- : shaderProps;
719
+ set shader(shaderProps: IRendererShader | [kind: string, props: IRendererShaderProps]) {
720
+ this.lng.shader = isArray(shaderProps) ? renderer.createShader(...shaderProps) : shaderProps;
744
721
  }
745
722
 
746
723
  _sendToLightningAnimatable(name: string, value: number) {
@@ -748,9 +725,7 @@ export class ElementNode extends Object {
748
725
  this.transition &&
749
726
  this.rendered &&
750
727
  Config.animationsEnabled &&
751
- (this.transition === true ||
752
- this.transition[name] ||
753
- this.transition[getPropertyAlias(name)])
728
+ (this.transition === true || this.transition[name] || this.transition[getPropertyAlias(name)])
754
729
  ) {
755
730
  const animationSettings =
756
731
  this.transition === true || this.transition[name] === true
@@ -762,29 +737,20 @@ export class ElementNode extends Object {
762
737
  this,
763
738
  name,
764
739
  value,
765
- animationSettings ||
766
- (this.animationSettings as SimpleAnimationSettings),
740
+ animationSettings || (this.animationSettings as SimpleAnimationSettings),
767
741
  );
768
742
  simpleAnimation.register(renderer.stage);
769
743
  return;
770
744
  } else {
771
- const animationController = this.animate(
772
- { [name]: value },
773
- animationSettings,
774
- );
745
+ const animationController = this.animate({ [name]: value }, animationSettings);
775
746
 
776
747
  if (this.onAnimation) {
777
- const animationEvents = Object.keys(
778
- this.onAnimation,
779
- ) as AnimationEvents[];
748
+ const animationEvents = Object.keys(this.onAnimation) as AnimationEvents[];
780
749
  for (const event of animationEvents) {
781
750
  const handler = this.onAnimation[event];
782
- animationController.on(
783
- event,
784
- (controller: IAnimationController, props?: any) => {
785
- handler!.call(this, controller, name, value, props);
786
- },
787
- );
751
+ animationController.on(event, (controller: IAnimationController, props?: any) => {
752
+ handler!.call(this, controller, name, value, props);
753
+ });
788
754
  }
789
755
  }
790
756
 
@@ -799,18 +765,11 @@ export class ElementNode extends Object {
799
765
  props: Partial<INodeAnimateProps<CoreShaderNode>>,
800
766
  animationSettings?: AnimationSettings,
801
767
  ): IAnimationController {
802
- isDev &&
803
- assertTruthy(this.rendered, 'Node must be rendered before animating');
804
- return (this.lng as IRendererNode).animate(
805
- props,
806
- animationSettings || this.animationSettings || {},
807
- );
768
+ isDev && assertTruthy(this.rendered, 'Node must be rendered before animating');
769
+ return (this.lng as IRendererNode).animate(props, animationSettings || this.animationSettings || {});
808
770
  }
809
771
 
810
- chain(
811
- props: Partial<INodeAnimateProps<CoreShaderNode>>,
812
- animationSettings?: AnimationSettings,
813
- ) {
772
+ chain(props: Partial<INodeAnimateProps<CoreShaderNode>>, animationSettings?: AnimationSettings) {
814
773
  if (this._animationRunning) {
815
774
  this._animationQueue = [];
816
775
  this._animationRunning = false;
@@ -819,8 +778,7 @@ export class ElementNode extends Object {
819
778
  if (animationSettings) {
820
779
  this._animationQueueSettings = animationSettings;
821
780
  } else if (!this._animationQueueSettings) {
822
- this._animationQueueSettings =
823
- animationSettings || this.animationSettings;
781
+ this._animationQueueSettings = animationSettings || this.animationSettings;
824
782
  }
825
783
  animationSettings = animationSettings || this._animationQueueSettings;
826
784
  this._animationQueue = this._animationQueue || [];
@@ -832,9 +790,7 @@ export class ElementNode extends Object {
832
790
  let animation = this._animationQueue!.shift();
833
791
  while (animation) {
834
792
  this._animationRunning = true;
835
- await this.animate(animation.props, animation.animationSettings)
836
- .start()
837
- .waitUntilStopped();
793
+ await this.animate(animation.props, animation.animationSettings).start().waitUntilStopped();
838
794
  animation = this._animationQueue!.shift();
839
795
  }
840
796
  this._animationRunning = false;
@@ -866,8 +822,7 @@ export class ElementNode extends Object {
866
822
  return;
867
823
  }
868
824
  } else {
869
- const focusedIndex =
870
- typeof this.forwardFocus === 'number' ? this.forwardFocus : null;
825
+ const focusedIndex = typeof this.forwardFocus === 'number' ? this.forwardFocus : null;
871
826
  const nodes = this.children;
872
827
  if (focusedIndex !== null && focusedIndex < nodes.length) {
873
828
  const child = nodes[focusedIndex];
@@ -991,9 +946,7 @@ export class ElementNode extends Object {
991
946
  }
992
947
 
993
948
  set states(states: NodeStates) {
994
- this._states = this._states
995
- ? this._states.merge(states)
996
- : new States(this._stateChanged.bind(this), states);
949
+ this._states = this._states ? this._states.merge(states) : new States(this._stateChanged.bind(this), states);
997
950
  if (this.rendered) {
998
951
  this._stateChanged();
999
952
  }
@@ -1061,8 +1014,7 @@ export class ElementNode extends Object {
1061
1014
 
1062
1015
  const flexChanged = this.display === 'flex' && calculateFlex(this);
1063
1016
  layoutQueue.delete(this);
1064
- const onLayoutChanged =
1065
- isFunc(this.onLayout) && this.onLayout.call(this, this);
1017
+ const onLayoutChanged = isFunc(this.onLayout) && this.onLayout.call(this, this);
1066
1018
 
1067
1019
  if ((flexChanged || onLayoutChanged) && this.parent) {
1068
1020
  addToLayoutQueue(this.parent);
@@ -1119,9 +1071,7 @@ export class ElementNode extends Object {
1119
1071
  let newStyles: Styles;
1120
1072
  if (numStates === 1) {
1121
1073
  newStyles = this[states[0] as keyof Styles] as Styles;
1122
- newStyles = stylesToUndo
1123
- ? { ...stylesToUndo, ...newStyles }
1124
- : newStyles;
1074
+ newStyles = stylesToUndo ? { ...stylesToUndo, ...newStyles } : newStyles;
1125
1075
  } else {
1126
1076
  newStyles = states.reduce((acc, state) => {
1127
1077
  const styles = this[state];
@@ -1236,17 +1186,13 @@ export class ElementNode extends Object {
1236
1186
  }
1237
1187
 
1238
1188
  if (!textProps.maxWidth) {
1239
- textProps.maxWidth =
1240
- parentWidth - textProps.x! - (textProps.marginRight || 0);
1189
+ textProps.maxWidth = parentWidth - textProps.x! - (textProps.marginRight || 0);
1241
1190
  }
1242
1191
 
1243
1192
  if (textProps.contain === 'both' && !textProps.maxHeight) {
1244
- textProps.maxHeight =
1245
- parentHeight - textProps.y! - (textProps.marginBottom || 0);
1193
+ textProps.maxHeight = parentHeight - textProps.y! - (textProps.marginBottom || 0);
1246
1194
  } else if (textProps.maxLines === 1) {
1247
- textProps.maxHeight = (textProps.maxHeight ||
1248
- textProps.lineHeight ||
1249
- textProps.fontSize) as number;
1195
+ textProps.maxHeight = (textProps.maxHeight || textProps.lineHeight || textProps.fontSize) as number;
1250
1196
  }
1251
1197
 
1252
1198
  textProps.w = textProps.h = undefined;
@@ -1258,9 +1204,7 @@ export class ElementNode extends Object {
1258
1204
  }
1259
1205
 
1260
1206
  isDev && log('Rendering: ', this, props);
1261
- node.lng = renderer.createTextNode(
1262
- props as unknown as IRendererTextNodeProps,
1263
- );
1207
+ node.lng = renderer.createTextNode(props as unknown as IRendererTextNodeProps);
1264
1208
  if (parent.requiresLayout()) {
1265
1209
  if (!textProps.maxWidth || !textProps.maxHeight) {
1266
1210
  node._layoutOnLoad();
@@ -1390,9 +1334,7 @@ function createRawShaderAccessor<T>(key: keyof StyleEffects) {
1390
1334
  };
1391
1335
  }
1392
1336
 
1393
- function shaderAccessor<T extends Record<string, any> | number>(
1394
- key: 'border' | 'shadow' | 'rounded',
1395
- ) {
1337
+ function shaderAccessor<T extends Record<string, any> | number>(key: 'border' | 'shadow' | 'rounded') {
1396
1338
  return {
1397
1339
  set(this: ElementNode, value: T) {
1398
1340
  let target = this.lng.shader || {};
@@ -1401,17 +1343,12 @@ function shaderAccessor<T extends Record<string, any> | number>(
1401
1343
  if (this.lng.shader?.program) {
1402
1344
  target = this.lng.shader.props;
1403
1345
  const transitionKey = key === 'rounded' ? 'borderRadius' : key;
1404
- if (
1405
- this.transition &&
1406
- (this.transition === true || this.transition[transitionKey])
1407
- ) {
1346
+ if (this.transition && (this.transition === true || this.transition[transitionKey])) {
1408
1347
  target = {};
1409
1348
  animationSettings =
1410
1349
  this.transition === true || this.transition[transitionKey] === true
1411
1350
  ? undefined
1412
- : (this.transition[transitionKey] as
1413
- | undefined
1414
- | AnimationSettings);
1351
+ : (this.transition[transitionKey] as undefined | AnimationSettings);
1415
1352
  }
1416
1353
  }
1417
1354
 
@@ -1451,8 +1388,6 @@ Object.defineProperties(ElementNode.prototype, {
1451
1388
  rounded: shaderAccessor<BorderRadius>('rounded'),
1452
1389
  // Alias for rounded
1453
1390
  borderRadius: shaderAccessor<BorderRadius>('rounded'),
1454
- linearGradient:
1455
- createRawShaderAccessor<LinearGradientProps>('linearGradient'),
1456
- radialGradient:
1457
- createRawShaderAccessor<RadialGradientProps>('radialGradient'),
1391
+ linearGradient: createRawShaderAccessor<LinearGradientProps>('linearGradient'),
1392
+ radialGradient: createRawShaderAccessor<RadialGradientProps>('radialGradient'),
1458
1393
  });
@@ -9,12 +9,7 @@ import {
9
9
  ShaderRoundedProps,
10
10
  ShaderShadowProps,
11
11
  } from './shaders.js';
12
- import {
13
- EventHandlers,
14
- DefaultKeyMap,
15
- KeyHoldMap,
16
- FocusNode,
17
- } from './focusKeyTypes.js';
12
+ import { EventHandlers, DefaultKeyMap, KeyHoldMap, FocusNode } from './focusKeyTypes.js';
18
13
  import type { JSXElement } from 'solid-js';
19
14
 
20
15
  export type AnimationSettings = Partial<lngr.AnimationSettings>;
@@ -52,9 +47,7 @@ export type RemoveUnderscoreProps<T> = {
52
47
  [K in keyof T as K extends `_${string}` ? never : K]: T[K];
53
48
  };
54
49
 
55
- type RendererText = AddColorString<
56
- Partial<Omit<lngr.ITextNodeProps, 'debug' | 'shader' | 'parent'>>
57
- >;
50
+ type RendererText = AddColorString<Partial<Omit<lngr.ITextNodeProps, 'debug' | 'shader' | 'parent'>>>;
58
51
 
59
52
  type CleanElementNode = NewOmit<
60
53
  RemoveUnderscoreProps<ElementNode>,
@@ -80,10 +73,7 @@ type CleanElementNode = NewOmit<
80
73
  >;
81
74
  /** Node text, children of a ElementNode of type TextNode */
82
75
  export interface ElementText
83
- extends NewOmit<
84
- ElementNode,
85
- '_type' | 'parent' | 'children' | 'src' | 'scale'
86
- >,
76
+ extends NewOmit<ElementNode, '_type' | 'parent' | 'children' | 'src' | 'scale' | 'fontFamily'>,
87
77
  NewOmit<RendererText, 'x' | 'y' | 'w' | 'h'> {
88
78
  _type: 'textNode';
89
79
  parent?: ElementNode;
@@ -107,14 +97,7 @@ export interface NodeProps
107
97
  Partial<
108
98
  NewOmit<
109
99
  CleanElementNode,
110
- | 'children'
111
- | 'text'
112
- | 'lng'
113
- | 'rendered'
114
- | 'renderer'
115
- | 'emit'
116
- | 'preFlexwidth'
117
- | 'preFlexHeight'
100
+ 'children' | 'text' | 'lng' | 'rendered' | 'renderer' | 'emit' | 'preFlexwidth' | 'preFlexHeight'
118
101
  >
119
102
  > {
120
103
  states?: NodeStates;
@@ -189,10 +172,7 @@ type EventPayloadMap = {
189
172
 
190
173
  type NodeEvents = keyof EventPayloadMap;
191
174
 
192
- type EventHandler<E extends NodeEvents> = (
193
- target: ElementNode,
194
- event?: EventPayloadMap[E],
195
- ) => void;
175
+ type EventHandler<E extends NodeEvents> = (target: ElementNode, event?: EventPayloadMap[E]) => void;
196
176
 
197
177
  export type OnEvent = Partial<{
198
178
  [K in NodeEvents]: EventHandler<K>;
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from '@lightningtv/solid/jsx-runtime';
1
+ export type * from '@lightningtv/solid/jsx-runtime';
2
2
  export * from './core/index.js';
3
3
  export type * from './core/index.js';
4
4
  export type { KeyHandler, KeyMap } from './core/focusManager.js';
@@ -6,13 +6,4 @@ export * from './activeElement.js';
6
6
  export * from './utils.js';
7
7
  export * from './render.js';
8
8
  export * from './types.js';
9
- export {
10
- For,
11
- Show,
12
- Suspense,
13
- SuspenseList,
14
- Switch,
15
- Match,
16
- Index,
17
- ErrorBoundary,
18
- } from 'solid-js';
9
+ export { For, Show, Suspense, SuspenseList, Switch, Match, Index, ErrorBoundary } from 'solid-js';
@@ -1,4 +1,4 @@
1
- import { ElementNode, NodeProps, View } from '@lightningtv/solid';
1
+ import { ElementNode, NodeProps, View } from '@lightningtv/solid';
2
2
  import { Show } from 'solid-js';
3
3
 
4
4
  interface Props {
@@ -14,6 +14,19 @@ const DEFAULT_PROPS = {
14
14
  easing: 'ease-in-out',
15
15
  };
16
16
 
17
+ export const ALPHA_NONE = { alpha: 0 };
18
+ export const ALPHA_FULL = { alpha: 1 };
19
+
20
+ export function fadeIn(el: ElementNode): void {
21
+ if (!el?.lng?.animate) return;
22
+ el.alpha = 0;
23
+ el.animate(ALPHA_FULL).start();
24
+ }
25
+ export function fadeOut(el: ElementNode): Promise<void> {
26
+ if (!el?.lng?.animate) return Promise.resolve();
27
+ return el.animate(ALPHA_NONE).start().waitUntilStopped();
28
+ }
29
+
17
30
  export default function FadeInOut(props: Props & NodeProps) {
18
31
  const config = Object.assign({}, DEFAULT_PROPS, props.transition);
19
32
  function onCreate(elm: ElementNode) {
@@ -23,12 +36,12 @@ export default function FadeInOut(props: Props & NodeProps) {
23
36
 
24
37
  function onDestroy(elm: ElementNode) {
25
38
  elm.rtt = true;
26
- return elm.animate({ alpha: 0 }, { duration: config.duration, easing: config.easing })
27
- .start().waitUntilStopped();
39
+ return elm.animate({ alpha: 0 }, { duration: config.duration, easing: config.easing }).start().waitUntilStopped();
28
40
  }
29
41
 
30
42
  return (
31
43
  <Show when={props.when} keyed>
32
44
  <View {...props} onDestroy={onDestroy} onCreate={onCreate} />
33
- </Show>);
45
+ </Show>
46
+ );
34
47
  }
@@ -68,7 +68,7 @@ function createLazy<T>(
68
68
  if (itemLength != props.each.length) {
69
69
  itemLength = props.each.length;
70
70
  if (viewRef && !viewRef.noRefocus && lng.hasFocus(viewRef)) {
71
- queueMicrotask(viewRef.setFocus);
71
+ queueMicrotask(() => viewRef.setFocus());
72
72
  }
73
73
  }
74
74
  return props.each.slice(0, offset());
@@ -46,7 +46,7 @@ function createVirtual<T>(
46
46
  return props.uniformSize !== false;
47
47
  });
48
48
 
49
- type SliceState = { start: number; slice: T[]; selected: number, delta: number, shiftBy: number, atStart: boolean };
49
+ type SliceState = { start: number; slice: T[]; selected: number, delta: number, shiftBy: number, atStart: boolean; cursor: number };
50
50
  const [slice, setSlice] = s.createSignal<SliceState>({
51
51
  start: 0,
52
52
  slice: [],
@@ -54,6 +54,7 @@ function createVirtual<T>(
54
54
  delta: 0,
55
55
  shiftBy: 0,
56
56
  atStart: true,
57
+ cursor: 0,
57
58
  });
58
59
 
59
60
  function normalizeDeltaForWindow(delta: number, windowLen: number): number {
@@ -86,7 +87,7 @@ function createVirtual<T>(
86
87
 
87
88
  function computeSlice(c: number, delta: number, prev: SliceState): SliceState {
88
89
  const total = itemCount();
89
- if (total === 0) return { start: 0, slice: [], selected: 0, delta, shiftBy: 0, atStart: true };
90
+ if (total === 0) return { start: 0, slice: [], selected: 0, delta, shiftBy: 0, atStart: true, cursor: 0 };
90
91
 
91
92
  const length = props.displaySize + bufferSize();
92
93
  let start = prev.start;
@@ -186,7 +187,7 @@ function createVirtual<T>(
186
187
  atStart = false;
187
188
  } else {
188
189
  // ScrollToIndex was called
189
- if (Math.abs(c - prev.start) > 1) {
190
+ if (c !== prev.cursor) {
190
191
  start = c;
191
192
  if (c === 0) {
192
193
  atStart = true;
@@ -286,7 +287,7 @@ function createVirtual<T>(
286
287
  : items().slice(start, start + length);
287
288
  }
288
289
 
289
- const state: SliceState = { start, slice: newSlice, selected, delta, shiftBy, atStart };
290
+ const state: SliceState = { start, slice: newSlice, selected, delta, shiftBy, atStart, cursor: c };
290
291
 
291
292
  if (props.debugInfo) {
292
293
  console.log(`[Virtual]`, {
@@ -412,6 +413,15 @@ function createVirtual<T>(
412
413
  if (lng.hasFocus(viewRef)) {
413
414
  viewRef.children[activeIndex]?.setFocus();
414
415
  }
416
+
417
+ if (newState.shiftBy === 0) return;
418
+
419
+ const childSize = computeSize(slice().selected);
420
+ // Original Position is offset to support scrollToIndex
421
+ originalPosition = originalPosition ?? viewRef.lng[axis];
422
+ targetPosition = targetPosition ?? viewRef.lng[axis];
423
+
424
+ viewRef.lng[axis] = (viewRef.lng[axis] || 0) + (childSize * -1);
415
425
  });
416
426
  };
417
427
 
@@ -0,0 +1,60 @@
1
+ import { ElementNode, insertNode, type NodeProps, type NodeStyles } from '@lightningtv/solid';
2
+ import { createMemo, getOwner, onMount, runWithOwner, type Accessor } from 'solid-js';
3
+ import { fadeIn, fadeOut } from './FadeInOut.jsx';
4
+ import { chainFunctions } from '@lightningtv/solid/primitives';
5
+
6
+ export const BorderBoxStyle: NodeStyles = {
7
+ alpha: 0,
8
+ borderSpace: 6,
9
+ borderRadius: 20,
10
+ border: { color: 0xffffff, width: 2 },
11
+ };
12
+
13
+ type BorderProps = NodeProps & { borderSpace?: number };
14
+ const borderComponent = (props: BorderProps) => {
15
+ const space = createMemo(() => props.borderSpace ?? (BorderBoxStyle.borderSpace as number));
16
+ return (
17
+ <>
18
+ <view
19
+ skipFocus
20
+ onCreate={(el) => {
21
+ const parent = el.parent!;
22
+ el.width = parent.width + space() * 2;
23
+ el.height = parent.height + space() * 2;
24
+ fadeIn(el);
25
+ }}
26
+ onDestroy={fadeOut}
27
+ style={BorderBoxStyle}
28
+ x={-space()}
29
+ y={-space()}
30
+ {...props}
31
+ />
32
+ </>
33
+ );
34
+ };
35
+
36
+ // Solid directives can only be used on native root elements `view` and `text`
37
+ export default function borderbox(el: ElementNode, accessor: Accessor<BorderProps | true | undefined>) {
38
+ let border: ElementNode | null;
39
+ const owner = getOwner();
40
+
41
+ onMount(() => {
42
+ el.onFocusChanged = chainFunctions((f) => {
43
+ if (f) {
44
+ if (border) return;
45
+ runWithOwner(owner, () => {
46
+ const props = accessor();
47
+ border = borderComponent(
48
+ props === true || props === undefined ? ({} as NodeProps) : props,
49
+ ) as any as ElementNode;
50
+ insertNode(el, border);
51
+ border.render();
52
+ });
53
+ } else if (border) {
54
+ border.destroy();
55
+ el.removeChild(border!);
56
+ border = null;
57
+ }
58
+ }, el.onFocusChanged);
59
+ });
60
+ }
@@ -1,6 +1,7 @@
1
1
  export * from './useFocusManager.js';
2
2
  export * from './announcer/index.js';
3
3
  export * from './createInfiniteItems.js';
4
+ export * from './borderBox.jsx';
4
5
  export * from './useMouse.js';
5
6
  export * from './portal.jsx';
6
7
  export * from './Lazy.jsx';
@@ -23,11 +24,7 @@ export * from './VirtualGrid.jsx';
23
24
  export * from './Virtual.jsx';
24
25
  export * from './utils/withScrolling.js';
25
26
  export * from './createTag.jsx';
26
- export {
27
- type AnyFunction,
28
- chainFunctions,
29
- chainRefs,
30
- } from './utils/chainFunctions.js';
27
+ export { type AnyFunction, chainFunctions, chainRefs } from './utils/chainFunctions.js';
31
28
  export * from './utils/handleNavigation.js';
32
29
  export { createSpriteMap, type SpriteDef } from './utils/createSpriteMap.js';
33
30
  export { createBlurredImage } from './utils/createBlurredImage.js';