shaders 2.3.69 → 2.3.71

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 (80) hide show
  1. package/dist/svelte/index.js +76 -76
  2. package/dist/svelte/source/components/AngularBlur.svelte +287 -0
  3. package/dist/svelte/source/components/Ascii.svelte +288 -0
  4. package/dist/svelte/source/components/Aurora.svelte +292 -0
  5. package/dist/svelte/source/components/Beam.svelte +290 -0
  6. package/dist/svelte/source/components/Blob.svelte +289 -0
  7. package/dist/svelte/source/components/Blur.svelte +287 -0
  8. package/dist/svelte/source/components/BrightnessContrast.svelte +288 -0
  9. package/dist/svelte/source/components/Bulge.svelte +289 -0
  10. package/dist/svelte/source/components/CRTScreen.svelte +294 -0
  11. package/dist/svelte/source/components/ChannelBlur.svelte +289 -0
  12. package/dist/svelte/source/components/Checkerboard.svelte +288 -0
  13. package/dist/svelte/source/components/ChromaFlow.svelte +286 -0
  14. package/dist/svelte/source/components/ChromaticAberration.svelte +291 -0
  15. package/dist/svelte/source/components/Circle.svelte +289 -0
  16. package/dist/svelte/source/components/ConcentricSpin.svelte +290 -0
  17. package/dist/svelte/source/components/ContourLines.svelte +289 -0
  18. package/dist/svelte/source/components/CursorRipples.svelte +288 -0
  19. package/dist/svelte/source/components/CursorTrail.svelte +286 -0
  20. package/dist/svelte/source/components/DiffuseBlur.svelte +287 -0
  21. package/dist/svelte/source/components/Dither.svelte +288 -0
  22. package/dist/svelte/source/components/DotGrid.svelte +288 -0
  23. package/dist/svelte/source/components/Duotone.svelte +287 -0
  24. package/dist/svelte/source/components/FilmGrain.svelte +287 -0
  25. package/dist/svelte/source/components/FloatingParticles.svelte +290 -0
  26. package/dist/svelte/source/components/FlowField.svelte +288 -0
  27. package/dist/svelte/source/components/GlassTiles.svelte +290 -0
  28. package/dist/svelte/source/components/Glitch.svelte +292 -0
  29. package/dist/svelte/source/components/Glow.svelte +289 -0
  30. package/dist/svelte/source/components/Godrays.svelte +289 -0
  31. package/dist/svelte/source/components/Grayscale.svelte +286 -0
  32. package/dist/svelte/source/components/Grid.svelte +288 -0
  33. package/dist/svelte/source/components/GridDistortion.svelte +287 -0
  34. package/dist/svelte/source/components/Group.svelte +286 -0
  35. package/dist/svelte/source/components/Halftone.svelte +289 -0
  36. package/dist/svelte/source/components/HueShift.svelte +287 -0
  37. package/dist/svelte/source/components/ImageTexture.svelte +286 -0
  38. package/dist/svelte/source/components/Invert.svelte +286 -0
  39. package/dist/svelte/source/components/Kaleidoscope.svelte +288 -0
  40. package/dist/svelte/source/components/LensFlare.svelte +299 -0
  41. package/dist/svelte/source/components/LinearBlur.svelte +288 -0
  42. package/dist/svelte/source/components/LinearGradient.svelte +287 -0
  43. package/dist/svelte/source/components/Liquify.svelte +286 -0
  44. package/dist/svelte/source/components/Mirror.svelte +287 -0
  45. package/dist/svelte/source/components/Perspective.svelte +290 -0
  46. package/dist/svelte/source/components/Pixelate.svelte +287 -0
  47. package/dist/svelte/source/components/Plasma.svelte +290 -0
  48. package/dist/svelte/source/components/PolarCoordinates.svelte +289 -0
  49. package/dist/svelte/source/components/Posterize.svelte +287 -0
  50. package/dist/svelte/source/components/ProgressiveBlur.svelte +289 -0
  51. package/dist/svelte/source/components/RadialGradient.svelte +287 -0
  52. package/dist/svelte/source/components/RectangularCoordinates.svelte +288 -0
  53. package/dist/svelte/source/components/Ripples.svelte +290 -0
  54. package/dist/svelte/source/components/Saturation.svelte +287 -0
  55. package/dist/svelte/source/components/Sharpness.svelte +287 -0
  56. package/dist/svelte/source/components/Shatter.svelte +289 -0
  57. package/dist/svelte/source/components/SimplexNoise.svelte +288 -0
  58. package/dist/svelte/source/components/SineWave.svelte +291 -0
  59. package/dist/svelte/source/components/SolidColor.svelte +286 -0
  60. package/dist/svelte/source/components/Spherize.svelte +290 -0
  61. package/dist/svelte/source/components/Spiral.svelte +290 -0
  62. package/dist/svelte/source/components/Strands.svelte +289 -0
  63. package/dist/svelte/source/components/Stretch.svelte +289 -0
  64. package/dist/svelte/source/components/Stripes.svelte +291 -0
  65. package/dist/svelte/source/components/StudioBackground.svelte +298 -0
  66. package/dist/svelte/source/components/Swirl.svelte +288 -0
  67. package/dist/svelte/source/components/TiltShift.svelte +290 -0
  68. package/dist/svelte/source/components/Tint.svelte +287 -0
  69. package/dist/svelte/source/components/Tritone.svelte +287 -0
  70. package/dist/svelte/source/components/Twirl.svelte +287 -0
  71. package/dist/svelte/source/components/Vibrance.svelte +287 -0
  72. package/dist/svelte/source/components/VideoTexture.svelte +286 -0
  73. package/dist/svelte/source/components/WaveDistortion.svelte +289 -0
  74. package/dist/svelte/source/components/WebcamTexture.svelte +286 -0
  75. package/dist/svelte/source/components/ZoomBlur.svelte +287 -0
  76. package/dist/svelte/source/engine/Preview.svelte +320 -0
  77. package/dist/svelte/source/engine/PreviewRenderComponent.svelte +27 -0
  78. package/dist/svelte/source/engine/Shader.svelte +263 -0
  79. package/dist/svelte/source/index.js +77 -0
  80. package/package.json +2 -1
@@ -0,0 +1,289 @@
1
+ <script lang="ts">
2
+ import { getContext, setContext, onMount, onDestroy } from 'svelte';
3
+ import {
4
+ createUniformsMap,
5
+ type UniformsMap,
6
+ type BlendMode,
7
+ type NodeMetadata,
8
+ type PropConfig,
9
+ type MaskConfig,
10
+ type MapConfig,
11
+ type PropDriver,
12
+ type TransformConfig
13
+ } from '../../../core/index.js';
14
+ import {setColorSpaceMode} from '../../../core/utilities/transformations/index.js';
15
+
16
+ // @ts-ignore - this import is replaced at build time
17
+ import { componentDefinition, type ComponentProps } from '../../../core/shaders/Blob/index.js';
18
+
19
+ /**
20
+ * Define component props including blend mode, opacity, visibility, masking, and transformation
21
+ */
22
+ interface ExtendedComponentProps extends Partial<ComponentProps> {
23
+ softness?: ComponentProps['softness'] | PropDriver;
24
+ highlightIntensity?: ComponentProps['highlightIntensity'] | PropDriver;
25
+ speed?: ComponentProps['speed'] | PropDriver;
26
+ blendMode?: BlendMode;
27
+ opacity?: number;
28
+ visible?: boolean;
29
+ id?: string;
30
+ maskSource?: string;
31
+ maskType?: string;
32
+ renderOrder?: number;
33
+ transform?: Partial<TransformConfig>;
34
+ children?: import('svelte').Snippet;
35
+ }
36
+
37
+ function isPropDriver(value: unknown): value is PropDriver {
38
+ return typeof value === 'object' && value !== null && 'type' in value && (value as any).type === 'map';
39
+ }
40
+
41
+ /**
42
+ * Default transform configuration (optimized for zero overhead)
43
+ */
44
+ const DEFAULT_TRANSFORM: TransformConfig = {
45
+ offsetX: 0,
46
+ offsetY: 0,
47
+ rotation: 0,
48
+ scale: 1,
49
+ anchorX: 0.5,
50
+ anchorY: 0.5,
51
+ edges: 'transparent'
52
+ };
53
+
54
+ // Define the component props and their default values from the shader definition
55
+ const componentDefaults = {
56
+ blendMode: 'normal' as BlendMode,
57
+ visible: true,
58
+ // opacity intentionally has no default - handled by renderer
59
+ // transform intentionally has no default - handled by effectiveTransform
60
+ ...Object.entries(componentDefinition.props).reduce(
61
+ (acc, [key, config]) => {
62
+ acc[key] = (config as unknown as PropConfig<typeof config>).default;
63
+ return acc;
64
+ },
65
+ {} as Record<string, any>
66
+ )
67
+ };
68
+
69
+ // Declare props using Svelte 5's syntax
70
+ const props: ExtendedComponentProps = $props();
71
+
72
+ // Apply defaults manually since Svelte 5 doesn't have withDefaults equivalent
73
+ // Use $derived so the metadata $effect re-runs when any of these change at runtime
74
+ const blendMode = $derived(props.blendMode ?? componentDefaults.blendMode);
75
+ const opacity = $derived(props.opacity); // No default - handled by renderer
76
+ const visible = $derived(props.visible ?? componentDefaults.visible); // Default to true
77
+ const id = $derived(props.id);
78
+ const maskSource = $derived(props.maskSource);
79
+ const maskType = $derived(props.maskType);
80
+ const renderOrder = $derived(props.renderOrder);
81
+ const { children } = props;
82
+
83
+ // Collect PropDriver values from shader props into the maps metadata structure
84
+ const mapsFromProps = $derived.by(() => {
85
+ const maps: Record<string, MapConfig> = {};
86
+ for (const key of Object.keys(componentDefinition.props)) {
87
+ const val = (props as any)[key];
88
+ if (isPropDriver(val)) maps[key] = val as MapConfig;
89
+ }
90
+ return Object.keys(maps).length > 0 ? maps : undefined;
91
+ });
92
+
93
+ /**
94
+ * Computed transform that merges user-provided values with defaults
95
+ */
96
+ const effectiveTransform = $derived({
97
+ ...DEFAULT_TRANSFORM,
98
+ ...props.transform
99
+ });
100
+
101
+ /**
102
+ * FIRST: Get the parent ID from context BEFORE setting our own context
103
+ */
104
+ const parentId = getContext<string>('shaderParentId');
105
+ if (parentId === undefined) {
106
+ throw new Error('Shader components must be used inside an <Shader> component or another shader component');
107
+ }
108
+
109
+ /**
110
+ * Use the provided ID or generate a unique identifier for this component instance
111
+ */
112
+ const instanceId = (id ? id.replace(/[^a-zA-Z0-9_]/g, '_') : null) || Math.random().toString(36).substring(2, 10);
113
+
114
+ /**
115
+ * THEN: Provide our unique identifier to child components
116
+ */
117
+ setContext('shaderParentId', instanceId);
118
+
119
+ /**
120
+ * Creates a non-reactive object containing only props that differ from defaults
121
+ * This optimization prevents unnecessary GPU uniform updates for unchanged values
122
+ * Special props like blendMode and opacity are handled separately
123
+ */
124
+ const shaderReadyProps = $derived.by(() => {
125
+ let baseProps = { ...componentDefaults };
126
+
127
+ // Only include props that differ from defaults (excluding special props and PropDrivers)
128
+ for (const key in props) {
129
+ if (key !== 'blendMode' && key !== 'opacity' && key !== 'visible' &&
130
+ key !== 'id' && key !== 'maskSource' && key !== 'maskType' && key !== 'renderOrder' &&
131
+ key !== 'transform' && key !== 'children') {
132
+ const propValue = (props as any)[key];
133
+ if (isPropDriver(propValue)) continue; // PropDrivers go to metadata.maps, not uniforms
134
+ const defaultValue = (componentDefaults as any)[key];
135
+ if (propValue !== undefined && propValue !== defaultValue) {
136
+ (baseProps as any)[key] = propValue;
137
+ }
138
+ }
139
+ }
140
+ return baseProps;
141
+ });
142
+
143
+ /**
144
+ * Get the color space from the root Shader component.
145
+ * Used to set the global color space mode before creating uniforms.
146
+ */
147
+ const shaderColorSpace = getContext<() => 'p3-linear' | 'srgb'>('shaderColorSpace');
148
+
149
+ /**
150
+ * Creates the GPU uniform values map using only the changed props
151
+ * Set the global color space mode before creating uniforms so colors are transformed correctly
152
+ * Note: Intentionally captures initial value - props are immutable after initialization
153
+ */
154
+ if (shaderColorSpace) {
155
+ setColorSpaceMode(shaderColorSpace());
156
+ }
157
+ // svelte-ignore state_referenced_locally
158
+ const uniforms: UniformsMap = createUniformsMap(componentDefinition, shaderReadyProps, instanceId);
159
+
160
+ /**
161
+ * Get the node registration function from parent context
162
+ */
163
+ const parentRegister = getContext<(id: string, fragmentNodeFunc: any, parentId: string | null, metadata: NodeMetadata | null, uniforms: UniformsMap | null, componentDefinition: any) => void>('shaderNodeRegister');
164
+ if (parentRegister === undefined) {
165
+ throw new Error('Shader components must be used inside an <Shader> component or another shader component');
166
+ }
167
+
168
+ /**
169
+ * Get the uniform update function from parent context
170
+ */
171
+ const parentUniformUpdate = getContext<(nodeId: string, uniformName: string, value: any) => void>('shaderUniformUpdate');
172
+ if (parentUniformUpdate === undefined) {
173
+ throw new Error('Shader components require shaderUniformUpdate from parent');
174
+ }
175
+
176
+ /**
177
+ * Get the metadata update function from parent context
178
+ */
179
+ const parentMetadataUpdate = getContext<(nodeId: string, metadata: NodeMetadata) => void>('shaderMetadataUpdate');
180
+ if (parentMetadataUpdate === undefined) {
181
+ throw new Error('Shader components require shaderMetadataUpdate from parent');
182
+ }
183
+
184
+ // DOM marker ref for determining render order from template position
185
+ let orderMarker: HTMLSpanElement;
186
+
187
+ // Stores the DOM-detected render order
188
+ let detectedRenderOrder: number | undefined = undefined;
189
+
190
+ // Flag to track when component is registered
191
+ let isRegistered = $state(false);
192
+
193
+ // Setup uniform watchers with registration guard
194
+ Object.entries(uniforms).forEach(([propName, { uniform, transform }]) => {
195
+ $effect(() => {
196
+ // Only run after component is registered
197
+ if (!isRegistered) return;
198
+
199
+ if (uniform && uniform.value !== undefined) {
200
+ const newValue = (props as any)[propName];
201
+ if (newValue !== undefined && !isPropDriver(newValue)) {
202
+ // Send raw value - renderer will handle transformation
203
+ // PropDriver values go to metadata.maps, not uniforms
204
+ parentUniformUpdate(instanceId, propName, newValue);
205
+ }
206
+ }
207
+ });
208
+ });
209
+
210
+ // Watch blend mode, opacity, visibility, masking, transformations, and prop maps changes
211
+ $effect(() => {
212
+ // Only run after component is registered
213
+ if (!isRegistered) return;
214
+
215
+ const metadata: NodeMetadata = {
216
+ blendMode,
217
+ opacity,
218
+ visible: visible === false ? false : true,
219
+ id,
220
+ mask: maskSource ? {
221
+ source: maskSource,
222
+ type: (maskType || 'alpha') as MaskConfig['type']
223
+ } : undefined,
224
+ maps: mapsFromProps,
225
+ renderOrder: renderOrder ?? detectedRenderOrder,
226
+ transform: effectiveTransform
227
+ };
228
+ parentMetadataUpdate(instanceId, metadata);
229
+ });
230
+
231
+ // Register this component after mount to ensure parent is ready
232
+ onMount(() => {
233
+ // Register this component with safety check
234
+ if (componentDefinition && typeof componentDefinition.fragmentNode === 'function') {
235
+ parentRegister(
236
+ instanceId,
237
+ componentDefinition.fragmentNode,
238
+ parentId,
239
+ {
240
+ blendMode,
241
+ opacity,
242
+ visible: visible !== false ? true : false,
243
+ id,
244
+ mask: maskSource ? {
245
+ source: maskSource,
246
+ type: (maskType || 'alpha') as MaskConfig['type']
247
+ } as MaskConfig : undefined,
248
+ maps: mapsFromProps,
249
+ renderOrder: renderOrder ?? detectedRenderOrder,
250
+ transform: effectiveTransform
251
+ },
252
+ uniforms,
253
+ componentDefinition
254
+ );
255
+
256
+ // Set flag to enable effects after successful registration
257
+ isRegistered = true;
258
+
259
+ // Detect DOM position for correct render ordering
260
+ if (renderOrder === undefined && orderMarker) {
261
+ const parent = orderMarker.parentElement;
262
+ if (parent) {
263
+ const siblings = parent.querySelectorAll(':scope > [data-shader-id]');
264
+ const position = Array.from(siblings).indexOf(orderMarker);
265
+ if (position >= 0) {
266
+ detectedRenderOrder = position;
267
+ parentMetadataUpdate(instanceId, { renderOrder: position } as NodeMetadata);
268
+ }
269
+ }
270
+ }
271
+ } else {
272
+ console.error('componentDefinition.fragmentNode is not a function:', {
273
+ componentDefinition,
274
+ fragmentNode: componentDefinition?.fragmentNode,
275
+ type: typeof componentDefinition?.fragmentNode
276
+ });
277
+ }
278
+ });
279
+
280
+ // Clean up node from registry when component is unmounted
281
+ onDestroy(() => {
282
+ isRegistered = false;
283
+ parentRegister(instanceId, null, null, null, null);
284
+ });
285
+ </script>
286
+
287
+ <span bind:this={orderMarker} style="display:contents" data-shader-id={instanceId}>
288
+ {@render children?.()}
289
+ </span>
@@ -0,0 +1,287 @@
1
+ <script lang="ts">
2
+ import { getContext, setContext, onMount, onDestroy } from 'svelte';
3
+ import {
4
+ createUniformsMap,
5
+ type UniformsMap,
6
+ type BlendMode,
7
+ type NodeMetadata,
8
+ type PropConfig,
9
+ type MaskConfig,
10
+ type MapConfig,
11
+ type PropDriver,
12
+ type TransformConfig
13
+ } from '../../../core/index.js';
14
+ import {setColorSpaceMode} from '../../../core/utilities/transformations/index.js';
15
+
16
+ // @ts-ignore - this import is replaced at build time
17
+ import { componentDefinition, type ComponentProps } from '../../../core/shaders/Blur/index.js';
18
+
19
+ /**
20
+ * Define component props including blend mode, opacity, visibility, masking, and transformation
21
+ */
22
+ interface ExtendedComponentProps extends Partial<ComponentProps> {
23
+ intensity?: ComponentProps['intensity'] | PropDriver;
24
+ blendMode?: BlendMode;
25
+ opacity?: number;
26
+ visible?: boolean;
27
+ id?: string;
28
+ maskSource?: string;
29
+ maskType?: string;
30
+ renderOrder?: number;
31
+ transform?: Partial<TransformConfig>;
32
+ children?: import('svelte').Snippet;
33
+ }
34
+
35
+ function isPropDriver(value: unknown): value is PropDriver {
36
+ return typeof value === 'object' && value !== null && 'type' in value && (value as any).type === 'map';
37
+ }
38
+
39
+ /**
40
+ * Default transform configuration (optimized for zero overhead)
41
+ */
42
+ const DEFAULT_TRANSFORM: TransformConfig = {
43
+ offsetX: 0,
44
+ offsetY: 0,
45
+ rotation: 0,
46
+ scale: 1,
47
+ anchorX: 0.5,
48
+ anchorY: 0.5,
49
+ edges: 'transparent'
50
+ };
51
+
52
+ // Define the component props and their default values from the shader definition
53
+ const componentDefaults = {
54
+ blendMode: 'normal' as BlendMode,
55
+ visible: true,
56
+ // opacity intentionally has no default - handled by renderer
57
+ // transform intentionally has no default - handled by effectiveTransform
58
+ ...Object.entries(componentDefinition.props).reduce(
59
+ (acc, [key, config]) => {
60
+ acc[key] = (config as unknown as PropConfig<typeof config>).default;
61
+ return acc;
62
+ },
63
+ {} as Record<string, any>
64
+ )
65
+ };
66
+
67
+ // Declare props using Svelte 5's syntax
68
+ const props: ExtendedComponentProps = $props();
69
+
70
+ // Apply defaults manually since Svelte 5 doesn't have withDefaults equivalent
71
+ // Use $derived so the metadata $effect re-runs when any of these change at runtime
72
+ const blendMode = $derived(props.blendMode ?? componentDefaults.blendMode);
73
+ const opacity = $derived(props.opacity); // No default - handled by renderer
74
+ const visible = $derived(props.visible ?? componentDefaults.visible); // Default to true
75
+ const id = $derived(props.id);
76
+ const maskSource = $derived(props.maskSource);
77
+ const maskType = $derived(props.maskType);
78
+ const renderOrder = $derived(props.renderOrder);
79
+ const { children } = props;
80
+
81
+ // Collect PropDriver values from shader props into the maps metadata structure
82
+ const mapsFromProps = $derived.by(() => {
83
+ const maps: Record<string, MapConfig> = {};
84
+ for (const key of Object.keys(componentDefinition.props)) {
85
+ const val = (props as any)[key];
86
+ if (isPropDriver(val)) maps[key] = val as MapConfig;
87
+ }
88
+ return Object.keys(maps).length > 0 ? maps : undefined;
89
+ });
90
+
91
+ /**
92
+ * Computed transform that merges user-provided values with defaults
93
+ */
94
+ const effectiveTransform = $derived({
95
+ ...DEFAULT_TRANSFORM,
96
+ ...props.transform
97
+ });
98
+
99
+ /**
100
+ * FIRST: Get the parent ID from context BEFORE setting our own context
101
+ */
102
+ const parentId = getContext<string>('shaderParentId');
103
+ if (parentId === undefined) {
104
+ throw new Error('Shader components must be used inside an <Shader> component or another shader component');
105
+ }
106
+
107
+ /**
108
+ * Use the provided ID or generate a unique identifier for this component instance
109
+ */
110
+ const instanceId = (id ? id.replace(/[^a-zA-Z0-9_]/g, '_') : null) || Math.random().toString(36).substring(2, 10);
111
+
112
+ /**
113
+ * THEN: Provide our unique identifier to child components
114
+ */
115
+ setContext('shaderParentId', instanceId);
116
+
117
+ /**
118
+ * Creates a non-reactive object containing only props that differ from defaults
119
+ * This optimization prevents unnecessary GPU uniform updates for unchanged values
120
+ * Special props like blendMode and opacity are handled separately
121
+ */
122
+ const shaderReadyProps = $derived.by(() => {
123
+ let baseProps = { ...componentDefaults };
124
+
125
+ // Only include props that differ from defaults (excluding special props and PropDrivers)
126
+ for (const key in props) {
127
+ if (key !== 'blendMode' && key !== 'opacity' && key !== 'visible' &&
128
+ key !== 'id' && key !== 'maskSource' && key !== 'maskType' && key !== 'renderOrder' &&
129
+ key !== 'transform' && key !== 'children') {
130
+ const propValue = (props as any)[key];
131
+ if (isPropDriver(propValue)) continue; // PropDrivers go to metadata.maps, not uniforms
132
+ const defaultValue = (componentDefaults as any)[key];
133
+ if (propValue !== undefined && propValue !== defaultValue) {
134
+ (baseProps as any)[key] = propValue;
135
+ }
136
+ }
137
+ }
138
+ return baseProps;
139
+ });
140
+
141
+ /**
142
+ * Get the color space from the root Shader component.
143
+ * Used to set the global color space mode before creating uniforms.
144
+ */
145
+ const shaderColorSpace = getContext<() => 'p3-linear' | 'srgb'>('shaderColorSpace');
146
+
147
+ /**
148
+ * Creates the GPU uniform values map using only the changed props
149
+ * Set the global color space mode before creating uniforms so colors are transformed correctly
150
+ * Note: Intentionally captures initial value - props are immutable after initialization
151
+ */
152
+ if (shaderColorSpace) {
153
+ setColorSpaceMode(shaderColorSpace());
154
+ }
155
+ // svelte-ignore state_referenced_locally
156
+ const uniforms: UniformsMap = createUniformsMap(componentDefinition, shaderReadyProps, instanceId);
157
+
158
+ /**
159
+ * Get the node registration function from parent context
160
+ */
161
+ const parentRegister = getContext<(id: string, fragmentNodeFunc: any, parentId: string | null, metadata: NodeMetadata | null, uniforms: UniformsMap | null, componentDefinition: any) => void>('shaderNodeRegister');
162
+ if (parentRegister === undefined) {
163
+ throw new Error('Shader components must be used inside an <Shader> component or another shader component');
164
+ }
165
+
166
+ /**
167
+ * Get the uniform update function from parent context
168
+ */
169
+ const parentUniformUpdate = getContext<(nodeId: string, uniformName: string, value: any) => void>('shaderUniformUpdate');
170
+ if (parentUniformUpdate === undefined) {
171
+ throw new Error('Shader components require shaderUniformUpdate from parent');
172
+ }
173
+
174
+ /**
175
+ * Get the metadata update function from parent context
176
+ */
177
+ const parentMetadataUpdate = getContext<(nodeId: string, metadata: NodeMetadata) => void>('shaderMetadataUpdate');
178
+ if (parentMetadataUpdate === undefined) {
179
+ throw new Error('Shader components require shaderMetadataUpdate from parent');
180
+ }
181
+
182
+ // DOM marker ref for determining render order from template position
183
+ let orderMarker: HTMLSpanElement;
184
+
185
+ // Stores the DOM-detected render order
186
+ let detectedRenderOrder: number | undefined = undefined;
187
+
188
+ // Flag to track when component is registered
189
+ let isRegistered = $state(false);
190
+
191
+ // Setup uniform watchers with registration guard
192
+ Object.entries(uniforms).forEach(([propName, { uniform, transform }]) => {
193
+ $effect(() => {
194
+ // Only run after component is registered
195
+ if (!isRegistered) return;
196
+
197
+ if (uniform && uniform.value !== undefined) {
198
+ const newValue = (props as any)[propName];
199
+ if (newValue !== undefined && !isPropDriver(newValue)) {
200
+ // Send raw value - renderer will handle transformation
201
+ // PropDriver values go to metadata.maps, not uniforms
202
+ parentUniformUpdate(instanceId, propName, newValue);
203
+ }
204
+ }
205
+ });
206
+ });
207
+
208
+ // Watch blend mode, opacity, visibility, masking, transformations, and prop maps changes
209
+ $effect(() => {
210
+ // Only run after component is registered
211
+ if (!isRegistered) return;
212
+
213
+ const metadata: NodeMetadata = {
214
+ blendMode,
215
+ opacity,
216
+ visible: visible === false ? false : true,
217
+ id,
218
+ mask: maskSource ? {
219
+ source: maskSource,
220
+ type: (maskType || 'alpha') as MaskConfig['type']
221
+ } : undefined,
222
+ maps: mapsFromProps,
223
+ renderOrder: renderOrder ?? detectedRenderOrder,
224
+ transform: effectiveTransform
225
+ };
226
+ parentMetadataUpdate(instanceId, metadata);
227
+ });
228
+
229
+ // Register this component after mount to ensure parent is ready
230
+ onMount(() => {
231
+ // Register this component with safety check
232
+ if (componentDefinition && typeof componentDefinition.fragmentNode === 'function') {
233
+ parentRegister(
234
+ instanceId,
235
+ componentDefinition.fragmentNode,
236
+ parentId,
237
+ {
238
+ blendMode,
239
+ opacity,
240
+ visible: visible !== false ? true : false,
241
+ id,
242
+ mask: maskSource ? {
243
+ source: maskSource,
244
+ type: (maskType || 'alpha') as MaskConfig['type']
245
+ } as MaskConfig : undefined,
246
+ maps: mapsFromProps,
247
+ renderOrder: renderOrder ?? detectedRenderOrder,
248
+ transform: effectiveTransform
249
+ },
250
+ uniforms,
251
+ componentDefinition
252
+ );
253
+
254
+ // Set flag to enable effects after successful registration
255
+ isRegistered = true;
256
+
257
+ // Detect DOM position for correct render ordering
258
+ if (renderOrder === undefined && orderMarker) {
259
+ const parent = orderMarker.parentElement;
260
+ if (parent) {
261
+ const siblings = parent.querySelectorAll(':scope > [data-shader-id]');
262
+ const position = Array.from(siblings).indexOf(orderMarker);
263
+ if (position >= 0) {
264
+ detectedRenderOrder = position;
265
+ parentMetadataUpdate(instanceId, { renderOrder: position } as NodeMetadata);
266
+ }
267
+ }
268
+ }
269
+ } else {
270
+ console.error('componentDefinition.fragmentNode is not a function:', {
271
+ componentDefinition,
272
+ fragmentNode: componentDefinition?.fragmentNode,
273
+ type: typeof componentDefinition?.fragmentNode
274
+ });
275
+ }
276
+ });
277
+
278
+ // Clean up node from registry when component is unmounted
279
+ onDestroy(() => {
280
+ isRegistered = false;
281
+ parentRegister(instanceId, null, null, null, null);
282
+ });
283
+ </script>
284
+
285
+ <span bind:this={orderMarker} style="display:contents" data-shader-id={instanceId}>
286
+ {@render children?.()}
287
+ </span>