aboutsummaryrefslogtreecommitdiff
path: root/packages/integrations/preact/src/signals.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/preact/src/signals.ts')
-rw-r--r--packages/integrations/preact/src/signals.ts88
1 files changed, 88 insertions, 0 deletions
diff --git a/packages/integrations/preact/src/signals.ts b/packages/integrations/preact/src/signals.ts
new file mode 100644
index 000000000..88a50327a
--- /dev/null
+++ b/packages/integrations/preact/src/signals.ts
@@ -0,0 +1,88 @@
+import type { Context } from './context.js';
+import { incrementId } from './context.js';
+import type {
+ ArrayObjectMapping,
+ AstroPreactAttrs,
+ PropNameToSignalMap,
+ SignalLike,
+ SignalToKeyOrIndexMap,
+ Signals,
+} from './types.js';
+
+function isSignal(x: any): x is SignalLike {
+ return x != null && typeof x === 'object' && typeof x.peek === 'function' && 'value' in x;
+}
+
+export function restoreSignalsOnProps(ctx: Context, props: Record<string, any>) {
+ // Restore signal props that were mutated for serialization
+ let propMap: PropNameToSignalMap;
+ if (ctx.propsToSignals.has(props)) {
+ propMap = ctx.propsToSignals.get(props)!;
+ } else {
+ propMap = new Map();
+ ctx.propsToSignals.set(props, propMap);
+ }
+ for (const [key, signal] of propMap) {
+ props[key] = signal;
+ }
+ return propMap;
+}
+
+export function serializeSignals(
+ ctx: Context,
+ props: Record<string, any>,
+ attrs: AstroPreactAttrs,
+ map: PropNameToSignalMap,
+) {
+ // Check for signals
+ const signals: Signals = {};
+ for (const [key, value] of Object.entries(props)) {
+ const isPropArray = Array.isArray(value);
+ // `typeof null` is 'object' in JS, so we need to check for `null` specifically
+ const isPropObject =
+ !isSignal(value) && typeof props[key] === 'object' && props[key] !== null && !isPropArray;
+
+ if (isPropObject || isPropArray) {
+ const values = isPropObject ? Object.keys(props[key]) : value;
+ values.forEach((valueKey: number | string, valueIndex: number) => {
+ const signal = isPropObject ? props[key][valueKey] : valueKey;
+ if (isSignal(signal)) {
+ const keyOrIndex = isPropObject ? valueKey.toString() : valueIndex;
+
+ props[key] = isPropObject
+ ? Object.assign({}, props[key], { [keyOrIndex]: signal.peek() })
+ : props[key].map((v: SignalLike, i: number) =>
+ i === valueIndex ? [signal.peek(), i] : v,
+ );
+
+ const currentMap = (map.get(key) || []) as SignalToKeyOrIndexMap;
+ map.set(key, [...currentMap, [signal, keyOrIndex]]);
+
+ const currentSignals = (signals[key] || []) as ArrayObjectMapping;
+ signals[key] = [...currentSignals, [getSignalId(ctx, signal), keyOrIndex]];
+ }
+ });
+ } else if (isSignal(value)) {
+ // Set the value to the current signal value
+ // This mutates the props on purpose, so that it will be serialized correct.
+ props[key] = value.peek();
+ map.set(key, value);
+
+ signals[key] = getSignalId(ctx, value);
+ }
+ }
+
+ if (Object.keys(signals).length) {
+ attrs['data-preact-signals'] = JSON.stringify(signals);
+ }
+}
+
+function getSignalId(ctx: Context, item: SignalLike) {
+ let id = ctx.signals.get(item);
+ if (!id) {
+ id = incrementId(ctx);
+ ctx.signals.set(item, id);
+ }
+
+ return id;
+}