@lightningtv/solid 3.1.12 → 3.1.14

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.
@@ -8,7 +8,9 @@ import * as lng from '@lightningjs/renderer';
8
8
 
9
9
  import { EventEmitter } from '@lightningjs/renderer/utils';
10
10
  import { Config } from '../config.js';
11
+ import { FontLoadOptions } from '../intrinsicTypes.js';
11
12
  import type {
13
+ DomRendererMainSettings,
12
14
  ExtractProps,
13
15
  IRendererMain,
14
16
  IRendererNode,
@@ -17,22 +19,20 @@ import type {
17
19
  IRendererStage,
18
20
  IRendererTextNode,
19
21
  IRendererTextNodeProps,
20
- DomRendererMainSettings,
21
22
  } from './domRendererTypes.js';
22
23
  import {
23
- colorToRgba,
24
+ applyEasing,
25
+ applySubTextureScaling,
24
26
  buildGradientStops,
27
+ colorToRgba,
28
+ compactString,
25
29
  computeLegacyObjectFit,
26
- applySubTextureScaling,
30
+ computeRenderStateForNode,
27
31
  getNodeLineHeight,
28
- applyEasing,
29
32
  interpolateProp,
30
33
  isRenderStateInBounds,
31
34
  nodeHasTextureSource,
32
- computeRenderStateForNode,
33
- compactString,
34
35
  } from './domRendererUtils.js';
35
- import { FontLoadOptions } from '../intrinsicTypes.js';
36
36
 
37
37
  // Feature detection for legacy brousers
38
38
  const _styleRef: any =
@@ -315,11 +315,15 @@ function updateNodeStyles(node: DOMNode | DOMText) {
315
315
  switch (textProps.contain) {
316
316
  case 'width':
317
317
  if (textProps.maxWidth && textProps.maxWidth > 0) {
318
- style += `width: ${textProps.maxWidth}px;`;
318
+ if (node.textAlign === 'center') {
319
+ style += `width: ${textProps.maxWidth}px;`;
320
+ } else {
321
+ style += `max-width: ${textProps.maxWidth}px;`;
322
+ }
323
+ style += `overflow: hidden;`;
319
324
  } else {
320
325
  style += `width: 100%;`;
321
326
  }
322
- style += `overflow: hidden;`;
323
327
  break;
324
328
  case 'both': {
325
329
  let lineHeight = getNodeLineHeight(textProps);
@@ -514,27 +518,84 @@ function updateNodeStyles(node: DOMNode | DOMText) {
514
518
  let borderWidth = shaderProps['border-w'];
515
519
  let borderColor = shaderProps['border-color'];
516
520
  let borderGap = shaderProps['border-gap'] ?? 0;
517
- let borderAlign = shaderProps['border-align'] ?? 'outside';
521
+ let borderAlign = shaderProps['border-align'] ?? 'inside';
518
522
  let radius = shaderProps['radius'];
519
523
 
520
524
  // Border
525
+ const borderWidthIsNumber = typeof borderWidth === 'number';
526
+ const borderWidthIsArray = Array.isArray(borderWidth);
527
+ const borderWidthHasValue =
528
+ (borderWidthIsNumber && borderWidth !== 0) ||
529
+ (borderWidthIsArray &&
530
+ (borderWidth as number[]).some(
531
+ (w) => typeof w === 'number' && w !== 0,
532
+ ));
533
+
521
534
  if (
522
- typeof borderWidth === 'number' &&
523
- borderWidth !== 0 &&
535
+ borderWidthHasValue &&
524
536
  typeof borderColor === 'number' &&
525
537
  borderColor !== 0
526
538
  ) {
527
539
  const rgbaColor = colorToRgba(borderColor);
528
540
 
529
- let gap = borderGap;
530
- if (borderAlign === 'inside') {
531
- gap = -(borderWidth + borderGap);
532
- } else if (borderAlign === 'center') {
533
- gap = -(borderWidth / 2) + borderGap;
534
- }
541
+ if (borderWidthIsNumber) {
542
+ let insideWidth = 0;
543
+ let outsideWidth = 0;
535
544
 
536
- borderStyle += `outline: ${borderWidth}px solid ${rgbaColor};`;
537
- borderStyle += `outline-offset: ${gap}px;`;
545
+ if (borderAlign === 'inside') {
546
+ insideWidth = borderWidth;
547
+ } else if (borderAlign === 'center') {
548
+ insideWidth = borderWidth / 2;
549
+ outsideWidth = borderWidth / 2;
550
+ } else {
551
+ outsideWidth = borderWidth;
552
+ }
553
+
554
+ outsideWidth += borderGap;
555
+ insideWidth -= borderGap;
556
+
557
+ if (insideWidth < 0) {
558
+ outsideWidth += insideWidth;
559
+ insideWidth = 0;
560
+ }
561
+ if (outsideWidth < 0) {
562
+ insideWidth += outsideWidth;
563
+ outsideWidth = 0;
564
+ }
565
+
566
+ const shadows: string[] = [];
567
+ if (outsideWidth > 0) {
568
+ shadows.push(`0 0 0 ${outsideWidth}px ${rgbaColor}`);
569
+ }
570
+ if (insideWidth > 0) {
571
+ shadows.push(`inset 0 0 0 ${insideWidth}px ${rgbaColor}`);
572
+ }
573
+
574
+ if (shadows.length > 0) {
575
+ borderStyle += `box-shadow: ${shadows.join(', ')};`;
576
+ }
577
+ } else if (borderWidthIsArray) {
578
+ // Individual borders per side [top, right, bottom, left]
579
+ // Allow individual properties to override array values
580
+ const topWidth =
581
+ shaderProps['border-top'] ?? (borderWidth as number[])[0];
582
+ const rightWidth =
583
+ shaderProps['border-right'] ?? (borderWidth as number[])[1];
584
+ const bottomWidth =
585
+ shaderProps['border-bottom'] ?? (borderWidth as number[])[2];
586
+ const leftWidth =
587
+ shaderProps['border-left'] ?? (borderWidth as number[])[3];
588
+
589
+ const widths = [topWidth, rightWidth, bottomWidth, leftWidth];
590
+ const sides = ['top', 'right', 'bottom', 'left'] as const;
591
+
592
+ for (let i = 0; i < sides.length; i++) {
593
+ const width = widths[i];
594
+ if (typeof width === 'number' && width !== 0) {
595
+ borderStyle += `border-${sides[i]}: ${width}px solid ${rgbaColor};`;
596
+ }
597
+ }
598
+ }
538
599
  }
539
600
  // Rounded
540
601
  if (typeof radius === 'number' && radius > 0) {
@@ -826,7 +887,6 @@ type Size = { width: number; height: number };
826
887
 
827
888
  function getElSize(node: DOMNode): Size {
828
889
  const rawRect = node.div.getBoundingClientRect();
829
-
830
890
  const dpr = Config.rendererOptions?.deviceLogicalPixelRatio ?? 1;
831
891
  let width = rawRect.width / dpr;
832
892
  let height = rawRect.height / dpr;
@@ -857,11 +917,17 @@ function getElSize(node: DOMNode): Size {
857
917
  */
858
918
  function updateDOMTextSize(node: DOMText): void {
859
919
  let size: Size;
920
+ let dimensionsChanged = false;
860
921
  switch (node.contain) {
861
922
  case 'width':
862
923
  size = getElSize(node);
924
+ if (node.props.w !== size.width) {
925
+ node.w = size.width;
926
+ dimensionsChanged = true;
927
+ }
863
928
  if (node.props.h !== size.height) {
864
929
  node.h = size.height;
930
+ dimensionsChanged = true;
865
931
  }
866
932
  break;
867
933
  case 'none':
@@ -869,16 +935,17 @@ function updateDOMTextSize(node: DOMText): void {
869
935
  if (node.props.h !== size.height || node.props.w !== size.width) {
870
936
  node.w = size.width;
871
937
  node.h = size.height;
938
+ dimensionsChanged = true;
872
939
  }
873
940
  break;
874
941
  }
875
942
 
876
- if (!node.loaded) {
943
+ if (!node.loaded || dimensionsChanged) {
877
944
  const payload: lng.NodeTextLoadedPayload = {
878
945
  type: 'text',
879
946
  dimensions: {
880
- w: node.props.w,
881
- h: node.props.h,
947
+ w: node.w,
948
+ h: node.h,
882
949
  },
883
950
  };
884
951
  node.emit('loaded', payload);
@@ -1094,6 +1161,7 @@ export class DOMNode extends EventEmitter implements IRendererNode {
1094
1161
  if (child !== child.stage.root) {
1095
1162
  if (nodeHasTextureSource(child)) {
1096
1163
  const nextState = computeRenderStateForNode(child);
1164
+
1097
1165
  if (nextState != null) {
1098
1166
  child.updateRenderState(nextState);
1099
1167
  }
@@ -123,7 +123,6 @@ export interface IRendererMain extends IEventEmitter {
123
123
  createNode(props: Partial<IRendererNodeProps>): IRendererNode;
124
124
  createShader: typeof lng.RendererMain.prototype.createShader;
125
125
  createTexture: typeof lng.RendererMain.prototype.createTexture;
126
- //createEffect: typeof lng.RendererMain.prototype.createEffect;
127
126
  }
128
127
 
129
128
  export interface DomRendererMainSettings {
@@ -14,6 +14,7 @@ import {
14
14
  type OnEvent,
15
15
  NewOmit,
16
16
  SingleBorderStyle,
17
+ type DollarString,
17
18
  } from './intrinsicTypes.js';
18
19
  import States, { type NodeStates } from './states.js';
19
20
  import calculateFlexOld from './flex.js';
@@ -689,6 +690,11 @@ export interface ElementNode extends RendererNode, FocusNode {
689
690
  paddingRight?: number;
690
691
  paddingBottom?: number;
691
692
  paddingLeft?: number;
693
+ /**
694
+ * Defines the order in which state styles are applied for this element.
695
+ * Overrides the global `Config.stateOrder`.
696
+ */
697
+ stateOrder?: DollarString[];
692
698
  }
693
699
 
694
700
  export class ElementNode extends Object {
@@ -726,7 +732,7 @@ export class ElementNode extends Object {
726
732
  if (this.rendered) {
727
733
  if (!this.lng.shader) {
728
734
  this.lng.shader = Config.convertToShader(this, target);
729
- } else if (DOM_RENDERING) {
735
+ } else if (DOM_RENDERING && Config.domRendererEnabled) {
730
736
  this.lng.shader = this.lng.shader; // lng.shader is a setter, force style update
731
737
  }
732
738
  } else {
@@ -1024,7 +1030,7 @@ export class ElementNode extends Object {
1024
1030
  'reprocessUpdates' in renderer.stage &&
1025
1031
  renderer.stage.reprocessUpdates
1026
1032
  ) {
1027
- renderer.stage.reprocessUpdates();
1033
+ renderer.stage.reprocessUpdates(runLayout);
1028
1034
  }
1029
1035
  this.parent!.updateLayout();
1030
1036
  });
@@ -1264,7 +1270,23 @@ export class ElementNode extends Object {
1264
1270
  ? { ...stylesToUndo, ...newStyles }
1265
1271
  : newStyles;
1266
1272
  } else {
1267
- newStyles = states.reduce((acc, state) => {
1273
+ let sortedStates = states as DollarString[];
1274
+ const stateOrder = this.stateOrder || Config.stateOrder;
1275
+ if (stateOrder && stateOrder.length > 0) {
1276
+ sortedStates = states.slice().sort((a, b) => {
1277
+ const aIdx = stateOrder.indexOf(a);
1278
+ const bIdx = stateOrder.indexOf(b);
1279
+
1280
+ // If a state is in the stateOrder, it should have higher specificity
1281
+ // than states not in the stateOrder.
1282
+ if (aIdx !== -1 && bIdx === -1) return 1;
1283
+ if (aIdx === -1 && bIdx !== -1) return -1;
1284
+
1285
+ return aIdx - bIdx;
1286
+ });
1287
+ }
1288
+
1289
+ newStyles = sortedStates.reduce((acc, state) => {
1268
1290
  const styles = this[state];
1269
1291
  return styles ? { ...acc, ...styles } : acc;
1270
1292
  }, stylesToUndo || {});
@@ -1557,6 +1579,7 @@ export function shaderAccessor<T extends Record<string, any> | number>(
1557
1579
  this._effects[key] = value;
1558
1580
 
1559
1581
  let animationSettings: AnimationSettings | undefined;
1582
+
1560
1583
  if (this.lng.shader?.props) {
1561
1584
  target = this.lng.shader.props;
1562
1585
  const transitionKey = key === 'rounded' ? 'borderRadius' : key;
@@ -1583,6 +1606,8 @@ export function shaderAccessor<T extends Record<string, any> | number>(
1583
1606
  if (this.rendered) {
1584
1607
  if (!this.lng.shader) {
1585
1608
  this.lng.shader = Config.convertToShader(this, target);
1609
+ } else if (DOM_RENDERING && Config.domRendererEnabled) {
1610
+ this.lng.shader = this.lng.shader; // lng.shader is a setter, force style update
1586
1611
  }
1587
1612
  } else {
1588
1613
  this.lng.shader = target;
@@ -1,8 +1,8 @@
1
1
  import * as lng from '@lightningjs/renderer';
2
- import { DOMRendererMain, loadFontToDom } from './dom-renderer/domRenderer.js';
3
2
  import { Config, DOM_RENDERING } from './config.js';
4
- import { FontLoadOptions } from './intrinsicTypes.js';
3
+ import { DOMRendererMain, loadFontToDom } from './dom-renderer/domRenderer.js';
5
4
  import { DomRendererMainSettings } from './dom-renderer/domRendererTypes.js';
5
+ import { FontLoadOptions } from './intrinsicTypes.js';
6
6
 
7
7
  export type SdfFontType = 'ssdf' | 'msdf';
8
8
  // Global renderer instance: can be either the Lightning or DOM implementation
@@ -18,7 +18,7 @@ export {
18
18
  };
19
19
  export { WebGlShader };
20
20
 
21
- import { DOM_RENDERING, SHADERS_ENABLED } from './config.js';
21
+ import { Config, DOM_RENDERING, SHADERS_ENABLED } from './config.js';
22
22
  import type { CoreShaderManager } from './intrinsicTypes.js';
23
23
  import { IRendererShaderManager } from './dom-renderer/domRendererTypes.js';
24
24
 
@@ -122,17 +122,17 @@ function toValidVec4(value: unknown): Vec4 {
122
122
  export function registerDefaultShaderRounded(
123
123
  shManager: IRendererShaderManager,
124
124
  ) {
125
- if (SHADERS_ENABLED && !DOM_RENDERING)
125
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled))
126
126
  shManager.registerShaderType('rounded', defaultShaderRounded);
127
127
  }
128
128
  export function registerDefaultShaderShadow(shManager: CoreShaderManager) {
129
- if (SHADERS_ENABLED && !DOM_RENDERING)
129
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled))
130
130
  shManager.registerShaderType('shadow', defaultShaderShadow);
131
131
  }
132
132
  export function registerDefaultShaderRoundedWithBorder(
133
133
  shManager: CoreShaderManager,
134
134
  ) {
135
- if (SHADERS_ENABLED && !DOM_RENDERING)
135
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled))
136
136
  shManager.registerShaderType(
137
137
  'roundedWithBorder',
138
138
  defaultShaderRoundedWithBorder,
@@ -141,7 +141,7 @@ export function registerDefaultShaderRoundedWithBorder(
141
141
  export function registerDefaultShaderRoundedWithShadow(
142
142
  shManager: CoreShaderManager,
143
143
  ) {
144
- if (SHADERS_ENABLED && !DOM_RENDERING)
144
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled))
145
145
  shManager.registerShaderType(
146
146
  'roundedWithShadow',
147
147
  defaultShaderRoundedWithShadow,
@@ -150,31 +150,31 @@ export function registerDefaultShaderRoundedWithShadow(
150
150
  export function registerDefaultShaderRoundedWithBorderAndShadow(
151
151
  shManager: CoreShaderManager,
152
152
  ) {
153
- if (SHADERS_ENABLED && !DOM_RENDERING)
153
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled))
154
154
  shManager.registerShaderType(
155
155
  'roundedWithBorderWithShadow',
156
156
  defaultShaderRoundedWithBorderAndShadow,
157
157
  );
158
158
  }
159
159
  export function registerDefaultShaderHolePunch(shManager: CoreShaderManager) {
160
- if (SHADERS_ENABLED && !DOM_RENDERING)
160
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled))
161
161
  shManager.registerShaderType('holePunch', defaultShaderHolePunch);
162
162
  }
163
163
  export function registerDefaultShaderRadialGradient(
164
164
  shManager: CoreShaderManager,
165
165
  ) {
166
- if (SHADERS_ENABLED && !DOM_RENDERING)
166
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled))
167
167
  shManager.registerShaderType('radialGradient', defaultShaderRadialGradient);
168
168
  }
169
169
  export function registerDefaultShaderLinearGradient(
170
170
  shManager: CoreShaderManager,
171
171
  ) {
172
- if (SHADERS_ENABLED && !DOM_RENDERING)
172
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled))
173
173
  shManager.registerShaderType('linearGradient', defaultShaderLinearGradient);
174
174
  }
175
175
 
176
176
  export function registerDefaultShaders(shManager: CoreShaderManager) {
177
- if (SHADERS_ENABLED && !DOM_RENDERING) {
177
+ if (SHADERS_ENABLED && !(DOM_RENDERING && Config.domRendererEnabled)) {
178
178
  registerDefaultShaderRounded(shManager);
179
179
  registerDefaultShaderShadow(shManager);
180
180
  registerDefaultShaderRoundedWithBorder(shManager);
@@ -95,7 +95,7 @@ export function Grid<T>(props: GridProps<T>): JSX.Element {
95
95
 
96
96
  // Handle focus when items change - important for autofocus
97
97
  createEffect(() => {
98
- if (props.items && props.items.length > 0 && gridRef && gridRef.states.has('$focus')) {
98
+ if (props.items && props.items.length > 0 && gridRef && hasFocus(gridRef)) {
99
99
  queueMicrotask(focus)
100
100
  }
101
101
  })