@jfdevelops/react-multi-step-form 1.0.0-alpha.29 → 1.0.0-alpha.30

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.
@@ -1,20 +1,58 @@
1
- let __jfdevelops_multi_step_form_core__internals = require("@jfdevelops/multi-step-form-core/_internals");
2
1
  let react = require("react");
3
2
 
4
3
  //#region src/hooks/use-selector.ts
4
+ /**
5
+ * Deep equality check that compares values regardless of property order.
6
+ * Handles objects, arrays, primitives, and null/undefined.
7
+ */
8
+ function deepEqual(a, b) {
9
+ if (a === b) return true;
10
+ if (a == null || b == null) return a === b;
11
+ if (typeof a !== typeof b) return false;
12
+ if (typeof a !== "object") return false;
13
+ if (Array.isArray(a) && Array.isArray(b)) {
14
+ if (a.length !== b.length) return false;
15
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
16
+ return true;
17
+ }
18
+ if (Array.isArray(a) || Array.isArray(b)) return false;
19
+ const keysA = Object.keys(a);
20
+ const keysB = Object.keys(b);
21
+ if (keysA.length !== keysB.length) return false;
22
+ for (const key of keysA) {
23
+ if (!keysB.includes(key)) return false;
24
+ if (!deepEqual(a[key], b[key])) return false;
25
+ }
26
+ return true;
27
+ }
5
28
  function createUseSelector(createCtx, subscribe) {
6
- return (selector) => {
29
+ return (selectorFn, logger, debugOptions) => {
7
30
  const snapshotCacheRef = (0, react.useRef)(null);
8
- const selectorRef = (0, react.useRef)(selector);
9
- selectorRef.current = selector;
31
+ const selectorRef = (0, react.useRef)(selectorFn);
32
+ selectorRef.current = selectorFn;
10
33
  const getSnapshot = () => {
11
34
  const currentCtx = createCtx();
12
35
  const newValue = selectorRef.current(currentCtx);
13
36
  if (snapshotCacheRef.current === null) {
14
37
  snapshotCacheRef.current = { value: newValue };
38
+ logger?.info(debugOptions?.onInitialValue?.(newValue) ?? `Initial value: ${JSON.stringify(newValue)}`);
39
+ return newValue;
40
+ }
41
+ const isPrimitive = newValue === null || typeof newValue !== "object" && typeof newValue !== "function";
42
+ let hasChanged;
43
+ if (isPrimitive) hasChanged = !Object.is(snapshotCacheRef.current.value, newValue);
44
+ else {
45
+ const oldValue = snapshotCacheRef.current.value;
46
+ if (oldValue === newValue) hasChanged = false;
47
+ else hasChanged = !deepEqual(oldValue, newValue);
48
+ }
49
+ if (hasChanged) {
50
+ const oldValue = snapshotCacheRef.current.value;
51
+ snapshotCacheRef.current = { value: newValue };
52
+ logger?.info(debugOptions?.onValueChanged?.(oldValue, newValue) ?? `Value changed: ${JSON.stringify(oldValue)} -> ${JSON.stringify(newValue)}`);
15
53
  return newValue;
16
54
  }
17
- if (!__jfdevelops_multi_step_form_core__internals.path.equalsAtPaths({ value: snapshotCacheRef.current.value }, ["value"], newValue).ok) snapshotCacheRef.current = { value: newValue };
55
+ logger?.info(debugOptions?.onValueUnchanged?.(newValue) ?? `Value unchanged: ${JSON.stringify(newValue)}`);
18
56
  return snapshotCacheRef.current.value;
19
57
  };
20
58
  return (0, react.useSyncExternalStore)(subscribe, () => getSnapshot(), () => getSnapshot());
@@ -1 +1 @@
1
- {"version":3,"file":"use-selector.cjs","names":["path"],"sources":["../../src/hooks/use-selector.ts"],"sourcesContent":["import type {\n AnyResolvedStep,\n StepNumbers,\n HelperFnChosenSteps,\n Expand,\n HelperFnCtx,\n} from '@jfdevelops/multi-step-form-core';\nimport { path } from '@jfdevelops/multi-step-form-core/_internals';\nimport { useSyncExternalStore, useRef } from 'react';\n\nexport type UseSelector<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n> = ReturnType<typeof createUseSelector<TResolvedStep, TSteps, TChosenSteps>>;\nexport type SelectorFn<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>,\n TSelected\n> = (\n ctx: Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>\n) => TSelected;\n\nexport function createUseSelector<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n>(\n createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>,\n subscribe: (listener: () => void) => () => void\n) {\n return <selected>(\n selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, selected>\n ) => {\n const snapshotCacheRef = useRef<{ value: selected } | null>(null);\n const selectorRef = useRef(selector);\n\n // Update the selector ref on every render to ensure we always use the latest selector\n selectorRef.current = selector;\n\n const getSnapshot = () => {\n const currentCtx = createCtx();\n const newValue = selectorRef.current(currentCtx);\n\n // Cache the result to ensure stable reference\n if (snapshotCacheRef.current === null) {\n snapshotCacheRef.current = { value: newValue };\n return newValue;\n }\n\n // Only update if the value actually changed (deep comparison using path.equalsAtPaths)\n const comparisonResult = path.equalsAtPaths(\n { value: snapshotCacheRef.current.value },\n ['value'],\n newValue\n );\n\n if (!comparisonResult.ok) {\n snapshotCacheRef.current = { value: newValue };\n }\n\n return snapshotCacheRef.current.value;\n };\n\n return useSyncExternalStore(\n subscribe,\n () => getSnapshot(),\n () => getSnapshot()\n );\n };\n}\n"],"mappings":";;;;AAwBA,SAAgB,kBAKd,WACA,WACA;AACA,SACE,aACG;EACH,MAAM,qCAAsD,KAAK;EACjE,MAAM,gCAAqB,SAAS;AAGpC,cAAY,UAAU;EAEtB,MAAM,oBAAoB;GACxB,MAAM,aAAa,WAAW;GAC9B,MAAM,WAAW,YAAY,QAAQ,WAAW;AAGhD,OAAI,iBAAiB,YAAY,MAAM;AACrC,qBAAiB,UAAU,EAAE,OAAO,UAAU;AAC9C,WAAO;;AAUT,OAAI,CANqBA,kDAAK,cAC5B,EAAE,OAAO,iBAAiB,QAAQ,OAAO,EACzC,CAAC,QAAQ,EACT,SACD,CAEqB,GACpB,kBAAiB,UAAU,EAAE,OAAO,UAAU;AAGhD,UAAO,iBAAiB,QAAQ;;AAGlC,yCACE,iBACM,aAAa,QACb,aAAa,CACpB"}
1
+ {"version":3,"file":"use-selector.cjs","names":["hasChanged: boolean"],"sources":["../../src/hooks/use-selector.ts"],"sourcesContent":["import type {\n AnyResolvedStep,\n StepNumbers,\n HelperFnChosenSteps,\n Expand,\n HelperFnCtx,\n MultiStepFormLogger,\n MultiStepFormLoggerOptions,\n} from '@jfdevelops/multi-step-form-core';\nimport { useSyncExternalStore, useRef } from 'react';\n\n/**\n * Deep equality check that compares values regardless of property order.\n * Handles objects, arrays, primitives, and null/undefined.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n // Same reference or both are the same primitive value\n if (a === b) {\n return true;\n }\n\n // Handle null/undefined\n if (a == null || b == null) {\n return a === b;\n }\n\n // Type mismatch\n if (typeof a !== typeof b) {\n return false;\n }\n\n // Both are primitives (but not equal due to first check)\n if (typeof a !== 'object') {\n return false;\n }\n\n // Both are arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) {\n return false;\n }\n }\n return true;\n }\n\n // One is array, other is not\n if (Array.isArray(a) || Array.isArray(b)) {\n return false;\n }\n\n // Both are objects (not arrays)\n const keysA = Object.keys(a as Record<string, unknown>);\n const keysB = Object.keys(b as Record<string, unknown>);\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n // Check if all keys in A exist in B with equal values\n for (const key of keysA) {\n if (!keysB.includes(key)) {\n return false;\n }\n if (\n !deepEqual(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key]\n )\n ) {\n return false;\n }\n }\n\n return true;\n}\n\ntype PrefixOptions = {\n /**\n * The action to perform on the prefix.\n *\n * @default 'prepend'\n */\n action?: 'prepend' | 'append';\n /**\n * The value to add to the prefix.\n */\n value: string | ((prefix: string) => string);\n /**\n * The delimiter to use between the original prefix and the added prefix.\n * @default '-'\n * @example\n * ```tsx\n * <Selector debug={{ prefix: { action: 'prepend', value: 'MySelector', delimiter: '|' } }}>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n */\n delimiter?: string;\n};\n\n/**\n * Debug options for customizing logging behavior in the Selector component.\n * All options are optional - you can provide any combination of these functions\n * to customize how debug information is logged.\n */\nexport type DebugOptions<TSelected> = {\n /**\n * The prefix to use for the logger.\n *\n * If a string or function is provided, it will replace the default prefix.\n * If you need to prepend or append to the default prefix, you can provide {@linkcode PrefixOptions}.\n * @default 'MultiStepFormSchema-Selector'\n *\n * @example\n * ```tsx\n * <Selector debug={{ prefix: 'MySelector' }}>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n *\n */\n prefix?: MultiStepFormLoggerOptions['prefix'] | PrefixOptions;\n /**\n * Called when the Selector component renders with the selected value.\n * This is called on every render of the Selector component, regardless of\n * whether the selected value has changed.\n *\n * @param selected - The current selected value from the selector function\n */\n onRender?: (selected: TSelected) => string;\n\n /**\n * Called when the Selector component renders its children.\n * This is only called when the Selector has a children render prop.\n */\n onChildrenRender?: () => string;\n\n /**\n * Called when the initial value is set for the first time.\n * This is called once when the selector first evaluates and caches its value.\n *\n * @param value - The initial selected value\n */\n onInitialValue?: (value: TSelected) => string;\n\n /**\n * Called when the selected value changes from one value to another.\n * This is called whenever the selector detects that the value has changed\n * (using Object.is() for primitives or deep comparison for objects/arrays).\n *\n * @param oldValue - The previous selected value\n * @param newValue - The new selected value\n */\n onValueChanged?: (oldValue: TSelected, newValue: TSelected) => string;\n\n /**\n * Called when the selected value is checked but hasn't changed.\n * This is called when the selector evaluates but determines that the value\n * is the same as the previously cached value.\n *\n * @param value - The current selected value (unchanged from previous check)\n */\n onValueUnchanged?: (value: TSelected) => string;\n};\n\nexport type UseSelector<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n> = ReturnType<typeof createUseSelector<TResolvedStep, TSteps, TChosenSteps>>;\nexport type SelectorFn<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>,\n TSelected\n> = (\n ctx: Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>\n) => TSelected;\n\nexport function createUseSelector<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n>(\n createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>,\n subscribe: (listener: () => void) => () => void\n) {\n return <selected>(\n selectorFn: SelectorFn<TResolvedStep, TSteps, TChosenSteps, selected>,\n logger?: MultiStepFormLogger,\n debugOptions?: DebugOptions<selected>\n ) => {\n const snapshotCacheRef = useRef<{ value: selected } | null>(null);\n const selectorRef = useRef(selectorFn);\n\n // Update the selector ref on every render to ensure we always use the latest selector\n selectorRef.current = selectorFn;\n\n const getSnapshot = () => {\n const currentCtx = createCtx();\n const newValue = selectorRef.current(currentCtx);\n\n // Cache the result to ensure stable reference\n if (snapshotCacheRef.current === null) {\n snapshotCacheRef.current = { value: newValue };\n\n logger?.info(\n debugOptions?.onInitialValue?.(newValue) ??\n `Initial value: ${JSON.stringify(newValue)}`\n );\n\n return newValue;\n }\n\n // Check if the value actually changed\n // For primitive values, use Object.is() for fast comparison\n // For complex values, use deep comparison\n const isPrimitive =\n newValue === null ||\n (typeof newValue !== 'object' && typeof newValue !== 'function');\n\n let hasChanged: boolean;\n\n if (isPrimitive) {\n // Use Object.is() for primitives (faster and more reliable)\n hasChanged = !Object.is(snapshotCacheRef.current.value, newValue);\n } else {\n // For objects/arrays, we need to do a deep comparison\n // First check reference equality (fast path)\n const oldValue = snapshotCacheRef.current.value;\n if (oldValue === newValue) {\n hasChanged = false;\n } else {\n // Deep comparison using a proper deep equality function\n // This correctly handles property order differences\n hasChanged = !deepEqual(oldValue, newValue);\n }\n }\n\n if (hasChanged) {\n const oldValue = snapshotCacheRef.current.value;\n\n snapshotCacheRef.current = { value: newValue };\n\n logger?.info(\n debugOptions?.onValueChanged?.(oldValue, newValue) ??\n `Value changed: ${JSON.stringify(oldValue)} -> ${JSON.stringify(\n newValue\n )}`\n );\n // Return the new value so useSyncExternalStore can detect the change via Object.is()\n return newValue;\n }\n\n // Return the cached value to maintain stable reference when value hasn't changed\n logger?.info(\n debugOptions?.onValueUnchanged?.(newValue) ??\n `Value unchanged: ${JSON.stringify(newValue)}`\n );\n\n return snapshotCacheRef.current.value;\n };\n\n return useSyncExternalStore(\n subscribe,\n () => getSnapshot(),\n () => getSnapshot()\n );\n };\n}\n"],"mappings":";;;;;;;AAeA,SAAS,UAAU,GAAY,GAAqB;AAElD,KAAI,MAAM,EACR,QAAO;AAIT,KAAI,KAAK,QAAQ,KAAK,KACpB,QAAO,MAAM;AAIf,KAAI,OAAO,MAAM,OAAO,EACtB,QAAO;AAIT,KAAI,OAAO,MAAM,SACf,QAAO;AAIT,KAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,EAAE;AACxC,MAAI,EAAE,WAAW,EAAE,OACjB,QAAO;AAET,OAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,IAC5B,KAAI,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CACxB,QAAO;AAGX,SAAO;;AAIT,KAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,CACtC,QAAO;CAIT,MAAM,QAAQ,OAAO,KAAK,EAA6B;CACvD,MAAM,QAAQ,OAAO,KAAK,EAA6B;AAEvD,KAAI,MAAM,WAAW,MAAM,OACzB,QAAO;AAIT,MAAK,MAAM,OAAO,OAAO;AACvB,MAAI,CAAC,MAAM,SAAS,IAAI,CACtB,QAAO;AAET,MACE,CAAC,UACE,EAA8B,MAC9B,EAA8B,KAChC,CAED,QAAO;;AAIX,QAAO;;AA0GT,SAAgB,kBAKd,WACA,WACA;AACA,SACE,YACA,QACA,iBACG;EACH,MAAM,qCAAsD,KAAK;EACjE,MAAM,gCAAqB,WAAW;AAGtC,cAAY,UAAU;EAEtB,MAAM,oBAAoB;GACxB,MAAM,aAAa,WAAW;GAC9B,MAAM,WAAW,YAAY,QAAQ,WAAW;AAGhD,OAAI,iBAAiB,YAAY,MAAM;AACrC,qBAAiB,UAAU,EAAE,OAAO,UAAU;AAE9C,YAAQ,KACN,cAAc,iBAAiB,SAAS,IACtC,kBAAkB,KAAK,UAAU,SAAS,GAC7C;AAED,WAAO;;GAMT,MAAM,cACJ,aAAa,QACZ,OAAO,aAAa,YAAY,OAAO,aAAa;GAEvD,IAAIA;AAEJ,OAAI,YAEF,cAAa,CAAC,OAAO,GAAG,iBAAiB,QAAQ,OAAO,SAAS;QAC5D;IAGL,MAAM,WAAW,iBAAiB,QAAQ;AAC1C,QAAI,aAAa,SACf,cAAa;QAIb,cAAa,CAAC,UAAU,UAAU,SAAS;;AAI/C,OAAI,YAAY;IACd,MAAM,WAAW,iBAAiB,QAAQ;AAE1C,qBAAiB,UAAU,EAAE,OAAO,UAAU;AAE9C,YAAQ,KACN,cAAc,iBAAiB,UAAU,SAAS,IAChD,kBAAkB,KAAK,UAAU,SAAS,CAAC,MAAM,KAAK,UACpD,SACD,GACJ;AAED,WAAO;;AAIT,WAAQ,KACN,cAAc,mBAAmB,SAAS,IACxC,oBAAoB,KAAK,UAAU,SAAS,GAC/C;AAED,UAAO,iBAAiB,QAAQ;;AAGlC,yCACE,iBACM,aAAa,QACb,aAAa,CACpB"}
@@ -1,9 +1,92 @@
1
- import { AnyResolvedStep, Expand, HelperFnChosenSteps, HelperFnCtx, StepNumbers } from "@jfdevelops/multi-step-form-core";
1
+ import { AnyResolvedStep, Expand, HelperFnChosenSteps, HelperFnCtx, MultiStepFormLogger, MultiStepFormLoggerOptions, StepNumbers } from "@jfdevelops/multi-step-form-core";
2
2
 
3
3
  //#region src/hooks/use-selector.d.ts
4
+ type PrefixOptions = {
5
+ /**
6
+ * The action to perform on the prefix.
7
+ *
8
+ * @default 'prepend'
9
+ */
10
+ action?: 'prepend' | 'append';
11
+ /**
12
+ * The value to add to the prefix.
13
+ */
14
+ value: string | ((prefix: string) => string);
15
+ /**
16
+ * The delimiter to use between the original prefix and the added prefix.
17
+ * @default '-'
18
+ * @example
19
+ * ```tsx
20
+ * <Selector debug={{ prefix: { action: 'prepend', value: 'MySelector', delimiter: '|' } }}>
21
+ * {(value) => <p>First name: {value}</p>}
22
+ * </Selector>
23
+ * ```
24
+ */
25
+ delimiter?: string;
26
+ };
27
+ /**
28
+ * Debug options for customizing logging behavior in the Selector component.
29
+ * All options are optional - you can provide any combination of these functions
30
+ * to customize how debug information is logged.
31
+ */
32
+ type DebugOptions<TSelected> = {
33
+ /**
34
+ * The prefix to use for the logger.
35
+ *
36
+ * If a string or function is provided, it will replace the default prefix.
37
+ * If you need to prepend or append to the default prefix, you can provide {@linkcode PrefixOptions}.
38
+ * @default 'MultiStepFormSchema-Selector'
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * <Selector debug={{ prefix: 'MySelector' }}>
43
+ * {(value) => <p>First name: {value}</p>}
44
+ * </Selector>
45
+ * ```
46
+ *
47
+ */
48
+ prefix?: MultiStepFormLoggerOptions['prefix'] | PrefixOptions;
49
+ /**
50
+ * Called when the Selector component renders with the selected value.
51
+ * This is called on every render of the Selector component, regardless of
52
+ * whether the selected value has changed.
53
+ *
54
+ * @param selected - The current selected value from the selector function
55
+ */
56
+ onRender?: (selected: TSelected) => string;
57
+ /**
58
+ * Called when the Selector component renders its children.
59
+ * This is only called when the Selector has a children render prop.
60
+ */
61
+ onChildrenRender?: () => string;
62
+ /**
63
+ * Called when the initial value is set for the first time.
64
+ * This is called once when the selector first evaluates and caches its value.
65
+ *
66
+ * @param value - The initial selected value
67
+ */
68
+ onInitialValue?: (value: TSelected) => string;
69
+ /**
70
+ * Called when the selected value changes from one value to another.
71
+ * This is called whenever the selector detects that the value has changed
72
+ * (using Object.is() for primitives or deep comparison for objects/arrays).
73
+ *
74
+ * @param oldValue - The previous selected value
75
+ * @param newValue - The new selected value
76
+ */
77
+ onValueChanged?: (oldValue: TSelected, newValue: TSelected) => string;
78
+ /**
79
+ * Called when the selected value is checked but hasn't changed.
80
+ * This is called when the selector evaluates but determines that the value
81
+ * is the same as the previously cached value.
82
+ *
83
+ * @param value - The current selected value (unchanged from previous check)
84
+ */
85
+ onValueUnchanged?: (value: TSelected) => string;
86
+ };
4
87
  type UseSelector<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>> = ReturnType<typeof createUseSelector<TResolvedStep, TSteps, TChosenSteps>>;
5
88
  type SelectorFn<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>, TSelected> = (ctx: Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>) => TSelected;
6
- declare function createUseSelector<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>>(createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>, subscribe: (listener: () => void) => () => void): <selected>(selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, selected>) => selected;
89
+ declare function createUseSelector<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>>(createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>, subscribe: (listener: () => void) => () => void): <selected>(selectorFn: SelectorFn<TResolvedStep, TSteps, TChosenSteps, selected>, logger?: MultiStepFormLogger, debugOptions?: DebugOptions<selected>) => selected;
7
90
  //#endregion
8
- export { SelectorFn, UseSelector };
91
+ export { DebugOptions, SelectorFn, UseSelector };
9
92
  //# sourceMappingURL=use-selector.d.cts.map
@@ -1,9 +1,92 @@
1
- import { AnyResolvedStep, Expand, HelperFnChosenSteps, HelperFnCtx, StepNumbers } from "@jfdevelops/multi-step-form-core";
1
+ import { AnyResolvedStep, Expand, HelperFnChosenSteps, HelperFnCtx, MultiStepFormLogger, MultiStepFormLoggerOptions, StepNumbers } from "@jfdevelops/multi-step-form-core";
2
2
 
3
3
  //#region src/hooks/use-selector.d.ts
4
+ type PrefixOptions = {
5
+ /**
6
+ * The action to perform on the prefix.
7
+ *
8
+ * @default 'prepend'
9
+ */
10
+ action?: 'prepend' | 'append';
11
+ /**
12
+ * The value to add to the prefix.
13
+ */
14
+ value: string | ((prefix: string) => string);
15
+ /**
16
+ * The delimiter to use between the original prefix and the added prefix.
17
+ * @default '-'
18
+ * @example
19
+ * ```tsx
20
+ * <Selector debug={{ prefix: { action: 'prepend', value: 'MySelector', delimiter: '|' } }}>
21
+ * {(value) => <p>First name: {value}</p>}
22
+ * </Selector>
23
+ * ```
24
+ */
25
+ delimiter?: string;
26
+ };
27
+ /**
28
+ * Debug options for customizing logging behavior in the Selector component.
29
+ * All options are optional - you can provide any combination of these functions
30
+ * to customize how debug information is logged.
31
+ */
32
+ type DebugOptions<TSelected> = {
33
+ /**
34
+ * The prefix to use for the logger.
35
+ *
36
+ * If a string or function is provided, it will replace the default prefix.
37
+ * If you need to prepend or append to the default prefix, you can provide {@linkcode PrefixOptions}.
38
+ * @default 'MultiStepFormSchema-Selector'
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * <Selector debug={{ prefix: 'MySelector' }}>
43
+ * {(value) => <p>First name: {value}</p>}
44
+ * </Selector>
45
+ * ```
46
+ *
47
+ */
48
+ prefix?: MultiStepFormLoggerOptions['prefix'] | PrefixOptions;
49
+ /**
50
+ * Called when the Selector component renders with the selected value.
51
+ * This is called on every render of the Selector component, regardless of
52
+ * whether the selected value has changed.
53
+ *
54
+ * @param selected - The current selected value from the selector function
55
+ */
56
+ onRender?: (selected: TSelected) => string;
57
+ /**
58
+ * Called when the Selector component renders its children.
59
+ * This is only called when the Selector has a children render prop.
60
+ */
61
+ onChildrenRender?: () => string;
62
+ /**
63
+ * Called when the initial value is set for the first time.
64
+ * This is called once when the selector first evaluates and caches its value.
65
+ *
66
+ * @param value - The initial selected value
67
+ */
68
+ onInitialValue?: (value: TSelected) => string;
69
+ /**
70
+ * Called when the selected value changes from one value to another.
71
+ * This is called whenever the selector detects that the value has changed
72
+ * (using Object.is() for primitives or deep comparison for objects/arrays).
73
+ *
74
+ * @param oldValue - The previous selected value
75
+ * @param newValue - The new selected value
76
+ */
77
+ onValueChanged?: (oldValue: TSelected, newValue: TSelected) => string;
78
+ /**
79
+ * Called when the selected value is checked but hasn't changed.
80
+ * This is called when the selector evaluates but determines that the value
81
+ * is the same as the previously cached value.
82
+ *
83
+ * @param value - The current selected value (unchanged from previous check)
84
+ */
85
+ onValueUnchanged?: (value: TSelected) => string;
86
+ };
4
87
  type UseSelector<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>> = ReturnType<typeof createUseSelector<TResolvedStep, TSteps, TChosenSteps>>;
5
88
  type SelectorFn<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>, TSelected> = (ctx: Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>) => TSelected;
6
- declare function createUseSelector<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>>(createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>, subscribe: (listener: () => void) => () => void): <selected>(selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, selected>) => selected;
89
+ declare function createUseSelector<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>>(createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>, subscribe: (listener: () => void) => () => void): <selected>(selectorFn: SelectorFn<TResolvedStep, TSteps, TChosenSteps, selected>, logger?: MultiStepFormLogger, debugOptions?: DebugOptions<selected>) => selected;
7
90
  //#endregion
8
- export { SelectorFn, UseSelector };
91
+ export { DebugOptions, SelectorFn, UseSelector };
9
92
  //# sourceMappingURL=use-selector.d.mts.map
@@ -1,20 +1,58 @@
1
- import { path } from "@jfdevelops/multi-step-form-core/_internals";
2
1
  import { useRef, useSyncExternalStore } from "react";
3
2
 
4
3
  //#region src/hooks/use-selector.ts
4
+ /**
5
+ * Deep equality check that compares values regardless of property order.
6
+ * Handles objects, arrays, primitives, and null/undefined.
7
+ */
8
+ function deepEqual(a, b) {
9
+ if (a === b) return true;
10
+ if (a == null || b == null) return a === b;
11
+ if (typeof a !== typeof b) return false;
12
+ if (typeof a !== "object") return false;
13
+ if (Array.isArray(a) && Array.isArray(b)) {
14
+ if (a.length !== b.length) return false;
15
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
16
+ return true;
17
+ }
18
+ if (Array.isArray(a) || Array.isArray(b)) return false;
19
+ const keysA = Object.keys(a);
20
+ const keysB = Object.keys(b);
21
+ if (keysA.length !== keysB.length) return false;
22
+ for (const key of keysA) {
23
+ if (!keysB.includes(key)) return false;
24
+ if (!deepEqual(a[key], b[key])) return false;
25
+ }
26
+ return true;
27
+ }
5
28
  function createUseSelector(createCtx, subscribe) {
6
- return (selector) => {
29
+ return (selectorFn, logger, debugOptions) => {
7
30
  const snapshotCacheRef = useRef(null);
8
- const selectorRef = useRef(selector);
9
- selectorRef.current = selector;
31
+ const selectorRef = useRef(selectorFn);
32
+ selectorRef.current = selectorFn;
10
33
  const getSnapshot = () => {
11
34
  const currentCtx = createCtx();
12
35
  const newValue = selectorRef.current(currentCtx);
13
36
  if (snapshotCacheRef.current === null) {
14
37
  snapshotCacheRef.current = { value: newValue };
38
+ logger?.info(debugOptions?.onInitialValue?.(newValue) ?? `Initial value: ${JSON.stringify(newValue)}`);
39
+ return newValue;
40
+ }
41
+ const isPrimitive = newValue === null || typeof newValue !== "object" && typeof newValue !== "function";
42
+ let hasChanged;
43
+ if (isPrimitive) hasChanged = !Object.is(snapshotCacheRef.current.value, newValue);
44
+ else {
45
+ const oldValue = snapshotCacheRef.current.value;
46
+ if (oldValue === newValue) hasChanged = false;
47
+ else hasChanged = !deepEqual(oldValue, newValue);
48
+ }
49
+ if (hasChanged) {
50
+ const oldValue = snapshotCacheRef.current.value;
51
+ snapshotCacheRef.current = { value: newValue };
52
+ logger?.info(debugOptions?.onValueChanged?.(oldValue, newValue) ?? `Value changed: ${JSON.stringify(oldValue)} -> ${JSON.stringify(newValue)}`);
15
53
  return newValue;
16
54
  }
17
- if (!path.equalsAtPaths({ value: snapshotCacheRef.current.value }, ["value"], newValue).ok) snapshotCacheRef.current = { value: newValue };
55
+ logger?.info(debugOptions?.onValueUnchanged?.(newValue) ?? `Value unchanged: ${JSON.stringify(newValue)}`);
18
56
  return snapshotCacheRef.current.value;
19
57
  };
20
58
  return useSyncExternalStore(subscribe, () => getSnapshot(), () => getSnapshot());
@@ -1 +1 @@
1
- {"version":3,"file":"use-selector.mjs","names":[],"sources":["../../src/hooks/use-selector.ts"],"sourcesContent":["import type {\n AnyResolvedStep,\n StepNumbers,\n HelperFnChosenSteps,\n Expand,\n HelperFnCtx,\n} from '@jfdevelops/multi-step-form-core';\nimport { path } from '@jfdevelops/multi-step-form-core/_internals';\nimport { useSyncExternalStore, useRef } from 'react';\n\nexport type UseSelector<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n> = ReturnType<typeof createUseSelector<TResolvedStep, TSteps, TChosenSteps>>;\nexport type SelectorFn<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>,\n TSelected\n> = (\n ctx: Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>\n) => TSelected;\n\nexport function createUseSelector<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n>(\n createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>,\n subscribe: (listener: () => void) => () => void\n) {\n return <selected>(\n selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, selected>\n ) => {\n const snapshotCacheRef = useRef<{ value: selected } | null>(null);\n const selectorRef = useRef(selector);\n\n // Update the selector ref on every render to ensure we always use the latest selector\n selectorRef.current = selector;\n\n const getSnapshot = () => {\n const currentCtx = createCtx();\n const newValue = selectorRef.current(currentCtx);\n\n // Cache the result to ensure stable reference\n if (snapshotCacheRef.current === null) {\n snapshotCacheRef.current = { value: newValue };\n return newValue;\n }\n\n // Only update if the value actually changed (deep comparison using path.equalsAtPaths)\n const comparisonResult = path.equalsAtPaths(\n { value: snapshotCacheRef.current.value },\n ['value'],\n newValue\n );\n\n if (!comparisonResult.ok) {\n snapshotCacheRef.current = { value: newValue };\n }\n\n return snapshotCacheRef.current.value;\n };\n\n return useSyncExternalStore(\n subscribe,\n () => getSnapshot(),\n () => getSnapshot()\n );\n };\n}\n"],"mappings":";;;;AAwBA,SAAgB,kBAKd,WACA,WACA;AACA,SACE,aACG;EACH,MAAM,mBAAmB,OAAmC,KAAK;EACjE,MAAM,cAAc,OAAO,SAAS;AAGpC,cAAY,UAAU;EAEtB,MAAM,oBAAoB;GACxB,MAAM,aAAa,WAAW;GAC9B,MAAM,WAAW,YAAY,QAAQ,WAAW;AAGhD,OAAI,iBAAiB,YAAY,MAAM;AACrC,qBAAiB,UAAU,EAAE,OAAO,UAAU;AAC9C,WAAO;;AAUT,OAAI,CANqB,KAAK,cAC5B,EAAE,OAAO,iBAAiB,QAAQ,OAAO,EACzC,CAAC,QAAQ,EACT,SACD,CAEqB,GACpB,kBAAiB,UAAU,EAAE,OAAO,UAAU;AAGhD,UAAO,iBAAiB,QAAQ;;AAGlC,SAAO,qBACL,iBACM,aAAa,QACb,aAAa,CACpB"}
1
+ {"version":3,"file":"use-selector.mjs","names":["hasChanged: boolean"],"sources":["../../src/hooks/use-selector.ts"],"sourcesContent":["import type {\n AnyResolvedStep,\n StepNumbers,\n HelperFnChosenSteps,\n Expand,\n HelperFnCtx,\n MultiStepFormLogger,\n MultiStepFormLoggerOptions,\n} from '@jfdevelops/multi-step-form-core';\nimport { useSyncExternalStore, useRef } from 'react';\n\n/**\n * Deep equality check that compares values regardless of property order.\n * Handles objects, arrays, primitives, and null/undefined.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n // Same reference or both are the same primitive value\n if (a === b) {\n return true;\n }\n\n // Handle null/undefined\n if (a == null || b == null) {\n return a === b;\n }\n\n // Type mismatch\n if (typeof a !== typeof b) {\n return false;\n }\n\n // Both are primitives (but not equal due to first check)\n if (typeof a !== 'object') {\n return false;\n }\n\n // Both are arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) {\n return false;\n }\n }\n return true;\n }\n\n // One is array, other is not\n if (Array.isArray(a) || Array.isArray(b)) {\n return false;\n }\n\n // Both are objects (not arrays)\n const keysA = Object.keys(a as Record<string, unknown>);\n const keysB = Object.keys(b as Record<string, unknown>);\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n // Check if all keys in A exist in B with equal values\n for (const key of keysA) {\n if (!keysB.includes(key)) {\n return false;\n }\n if (\n !deepEqual(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key]\n )\n ) {\n return false;\n }\n }\n\n return true;\n}\n\ntype PrefixOptions = {\n /**\n * The action to perform on the prefix.\n *\n * @default 'prepend'\n */\n action?: 'prepend' | 'append';\n /**\n * The value to add to the prefix.\n */\n value: string | ((prefix: string) => string);\n /**\n * The delimiter to use between the original prefix and the added prefix.\n * @default '-'\n * @example\n * ```tsx\n * <Selector debug={{ prefix: { action: 'prepend', value: 'MySelector', delimiter: '|' } }}>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n */\n delimiter?: string;\n};\n\n/**\n * Debug options for customizing logging behavior in the Selector component.\n * All options are optional - you can provide any combination of these functions\n * to customize how debug information is logged.\n */\nexport type DebugOptions<TSelected> = {\n /**\n * The prefix to use for the logger.\n *\n * If a string or function is provided, it will replace the default prefix.\n * If you need to prepend or append to the default prefix, you can provide {@linkcode PrefixOptions}.\n * @default 'MultiStepFormSchema-Selector'\n *\n * @example\n * ```tsx\n * <Selector debug={{ prefix: 'MySelector' }}>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n *\n */\n prefix?: MultiStepFormLoggerOptions['prefix'] | PrefixOptions;\n /**\n * Called when the Selector component renders with the selected value.\n * This is called on every render of the Selector component, regardless of\n * whether the selected value has changed.\n *\n * @param selected - The current selected value from the selector function\n */\n onRender?: (selected: TSelected) => string;\n\n /**\n * Called when the Selector component renders its children.\n * This is only called when the Selector has a children render prop.\n */\n onChildrenRender?: () => string;\n\n /**\n * Called when the initial value is set for the first time.\n * This is called once when the selector first evaluates and caches its value.\n *\n * @param value - The initial selected value\n */\n onInitialValue?: (value: TSelected) => string;\n\n /**\n * Called when the selected value changes from one value to another.\n * This is called whenever the selector detects that the value has changed\n * (using Object.is() for primitives or deep comparison for objects/arrays).\n *\n * @param oldValue - The previous selected value\n * @param newValue - The new selected value\n */\n onValueChanged?: (oldValue: TSelected, newValue: TSelected) => string;\n\n /**\n * Called when the selected value is checked but hasn't changed.\n * This is called when the selector evaluates but determines that the value\n * is the same as the previously cached value.\n *\n * @param value - The current selected value (unchanged from previous check)\n */\n onValueUnchanged?: (value: TSelected) => string;\n};\n\nexport type UseSelector<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n> = ReturnType<typeof createUseSelector<TResolvedStep, TSteps, TChosenSteps>>;\nexport type SelectorFn<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>,\n TSelected\n> = (\n ctx: Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>\n) => TSelected;\n\nexport function createUseSelector<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n>(\n createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>,\n subscribe: (listener: () => void) => () => void\n) {\n return <selected>(\n selectorFn: SelectorFn<TResolvedStep, TSteps, TChosenSteps, selected>,\n logger?: MultiStepFormLogger,\n debugOptions?: DebugOptions<selected>\n ) => {\n const snapshotCacheRef = useRef<{ value: selected } | null>(null);\n const selectorRef = useRef(selectorFn);\n\n // Update the selector ref on every render to ensure we always use the latest selector\n selectorRef.current = selectorFn;\n\n const getSnapshot = () => {\n const currentCtx = createCtx();\n const newValue = selectorRef.current(currentCtx);\n\n // Cache the result to ensure stable reference\n if (snapshotCacheRef.current === null) {\n snapshotCacheRef.current = { value: newValue };\n\n logger?.info(\n debugOptions?.onInitialValue?.(newValue) ??\n `Initial value: ${JSON.stringify(newValue)}`\n );\n\n return newValue;\n }\n\n // Check if the value actually changed\n // For primitive values, use Object.is() for fast comparison\n // For complex values, use deep comparison\n const isPrimitive =\n newValue === null ||\n (typeof newValue !== 'object' && typeof newValue !== 'function');\n\n let hasChanged: boolean;\n\n if (isPrimitive) {\n // Use Object.is() for primitives (faster and more reliable)\n hasChanged = !Object.is(snapshotCacheRef.current.value, newValue);\n } else {\n // For objects/arrays, we need to do a deep comparison\n // First check reference equality (fast path)\n const oldValue = snapshotCacheRef.current.value;\n if (oldValue === newValue) {\n hasChanged = false;\n } else {\n // Deep comparison using a proper deep equality function\n // This correctly handles property order differences\n hasChanged = !deepEqual(oldValue, newValue);\n }\n }\n\n if (hasChanged) {\n const oldValue = snapshotCacheRef.current.value;\n\n snapshotCacheRef.current = { value: newValue };\n\n logger?.info(\n debugOptions?.onValueChanged?.(oldValue, newValue) ??\n `Value changed: ${JSON.stringify(oldValue)} -> ${JSON.stringify(\n newValue\n )}`\n );\n // Return the new value so useSyncExternalStore can detect the change via Object.is()\n return newValue;\n }\n\n // Return the cached value to maintain stable reference when value hasn't changed\n logger?.info(\n debugOptions?.onValueUnchanged?.(newValue) ??\n `Value unchanged: ${JSON.stringify(newValue)}`\n );\n\n return snapshotCacheRef.current.value;\n };\n\n return useSyncExternalStore(\n subscribe,\n () => getSnapshot(),\n () => getSnapshot()\n );\n };\n}\n"],"mappings":";;;;;;;AAeA,SAAS,UAAU,GAAY,GAAqB;AAElD,KAAI,MAAM,EACR,QAAO;AAIT,KAAI,KAAK,QAAQ,KAAK,KACpB,QAAO,MAAM;AAIf,KAAI,OAAO,MAAM,OAAO,EACtB,QAAO;AAIT,KAAI,OAAO,MAAM,SACf,QAAO;AAIT,KAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,EAAE;AACxC,MAAI,EAAE,WAAW,EAAE,OACjB,QAAO;AAET,OAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,IAC5B,KAAI,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CACxB,QAAO;AAGX,SAAO;;AAIT,KAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,CACtC,QAAO;CAIT,MAAM,QAAQ,OAAO,KAAK,EAA6B;CACvD,MAAM,QAAQ,OAAO,KAAK,EAA6B;AAEvD,KAAI,MAAM,WAAW,MAAM,OACzB,QAAO;AAIT,MAAK,MAAM,OAAO,OAAO;AACvB,MAAI,CAAC,MAAM,SAAS,IAAI,CACtB,QAAO;AAET,MACE,CAAC,UACE,EAA8B,MAC9B,EAA8B,KAChC,CAED,QAAO;;AAIX,QAAO;;AA0GT,SAAgB,kBAKd,WACA,WACA;AACA,SACE,YACA,QACA,iBACG;EACH,MAAM,mBAAmB,OAAmC,KAAK;EACjE,MAAM,cAAc,OAAO,WAAW;AAGtC,cAAY,UAAU;EAEtB,MAAM,oBAAoB;GACxB,MAAM,aAAa,WAAW;GAC9B,MAAM,WAAW,YAAY,QAAQ,WAAW;AAGhD,OAAI,iBAAiB,YAAY,MAAM;AACrC,qBAAiB,UAAU,EAAE,OAAO,UAAU;AAE9C,YAAQ,KACN,cAAc,iBAAiB,SAAS,IACtC,kBAAkB,KAAK,UAAU,SAAS,GAC7C;AAED,WAAO;;GAMT,MAAM,cACJ,aAAa,QACZ,OAAO,aAAa,YAAY,OAAO,aAAa;GAEvD,IAAIA;AAEJ,OAAI,YAEF,cAAa,CAAC,OAAO,GAAG,iBAAiB,QAAQ,OAAO,SAAS;QAC5D;IAGL,MAAM,WAAW,iBAAiB,QAAQ;AAC1C,QAAI,aAAa,SACf,cAAa;QAIb,cAAa,CAAC,UAAU,UAAU,SAAS;;AAI/C,OAAI,YAAY;IACd,MAAM,WAAW,iBAAiB,QAAQ;AAE1C,qBAAiB,UAAU,EAAE,OAAO,UAAU;AAE9C,YAAQ,KACN,cAAc,iBAAiB,UAAU,SAAS,IAChD,kBAAkB,KAAK,UAAU,SAAS,CAAC,MAAM,KAAK,UACpD,SACD,GACJ;AAED,WAAO;;AAIT,WAAQ,KACN,cAAc,mBAAmB,SAAS,IACxC,oBAAoB,KAAK,UAAU,SAAS,GAC/C;AAED,UAAO,iBAAiB,QAAQ;;AAGlC,SAAO,qBACL,iBACM,aAAa,QACb,aAAa,CACpB"}
package/dist/selector.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  const require_use_selector = require('./hooks/use-selector.cjs');
2
+ let __jfdevelops_multi_step_form_core = require("@jfdevelops/multi-step-form-core");
2
3
  let react = require("react");
3
4
  let react_jsx_runtime = require("react/jsx-runtime");
4
5
 
@@ -7,12 +8,37 @@ let selector;
7
8
  (function(_selector) {
8
9
  function create(createCtx, subscribe) {
9
10
  const useSelector = require_use_selector.createUseSelector(createCtx, subscribe);
10
- const Selector = ({ selector: selector$1, children }) => {
11
- const selected = useSelector(selector$1);
11
+ const Selector = ({ selector: selector$1, children, debug }) => {
12
+ const isDebugEnabled = typeof debug === "boolean" ? debug : debug !== void 0;
13
+ const debugOptions = typeof debug === "object" ? debug : void 0;
14
+ const prefix = debugOptions?.prefix;
15
+ const logger = (0, react.useMemo)(() => {
16
+ if (!isDebugEnabled) return;
17
+ return new __jfdevelops_multi_step_form_core.MultiStepFormLogger({
18
+ debug: true,
19
+ prefix: (defaultValue) => {
20
+ const defaultPrefix = `${defaultValue}-Selector`;
21
+ console.log(prefix);
22
+ if (!prefix) return defaultPrefix;
23
+ if (typeof prefix === "string") return prefix;
24
+ if (typeof prefix === "function") return prefix(defaultValue);
25
+ if (typeof prefix === "object") {
26
+ const { action = "prepend", value, delimiter = "-" } = prefix;
27
+ const resolvedValue = typeof value === "function" ? value(defaultValue) : value;
28
+ if (action === "prepend") return `${resolvedValue}${delimiter}${defaultPrefix}`;
29
+ if (action === "append") return `${defaultPrefix}${delimiter}${resolvedValue}`;
30
+ return defaultPrefix;
31
+ }
32
+ return defaultPrefix;
33
+ }
34
+ });
35
+ }, [isDebugEnabled, prefix]);
36
+ const selected = useSelector(selector$1, logger, debugOptions);
37
+ logger?.info(debugOptions?.onRender?.(selected) ?? `Rendering with selected: ${JSON.stringify(selected)}`);
12
38
  if (children) return (0, react.createElement)(react_jsx_runtime.Fragment, null, children(selected));
13
39
  return (0, react.createElement)(react_jsx_runtime.Fragment, null, String(selected ?? ""));
14
40
  };
15
- return (0, react.memo)(Selector, () => false);
41
+ return Selector;
16
42
  }
17
43
  _selector.create = create;
18
44
  })(selector || (selector = {}));
@@ -1 +1 @@
1
- {"version":3,"file":"selector.cjs","names":["createUseSelector","Selector: component<TResolvedStep, TSteps, TChosenSteps>","selector","Fragment"],"sources":["../src/selector.tsx"],"sourcesContent":["import {\n AnyResolvedStep,\n StepNumbers,\n HelperFnChosenSteps,\n Expand,\n HelperFnCtx,\n} from '@jfdevelops/multi-step-form-core';\nimport { createUseSelector, SelectorFn } from './hooks/use-selector';\nimport { Fragment } from 'react/jsx-runtime';\nimport { createElement, memo } from 'react';\n\nexport namespace selector {\n export type props<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>,\n TSelected\n > = {\n selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, TSelected>;\n children?: (selected: TSelected) => React.ReactNode;\n };\n export type component<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n > =\n /**\n * A component for reactively displaying a value from the form context.\n * Unlike `useSelector`, this component only re-renders itself, not the parent component.\n * Use this when you want to display a reactive value without causing parent re-renders.\n *\n * @param selector - A function that receives the current step's context and returns the selected value\n * @param children - Optional render prop that receives the selected value\n *\n * @example\n * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue}>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n */\n <TSelected>(\n props: props<TResolvedStep, TSteps, TChosenSteps, TSelected>\n ) => React.ReactNode;\n\n export function create<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n >(\n createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>,\n subscribe: (listener: () => void) => () => void\n ) {\n const useSelector = createUseSelector(createCtx, subscribe);\n const Selector: component<TResolvedStep, TSteps, TChosenSteps> = ({\n selector,\n children,\n }) => {\n const selected = useSelector(selector);\n\n if (children) {\n return createElement(Fragment, null, children(selected));\n }\n return createElement(Fragment, null, String(selected ?? ''));\n };\n\n return memo(\n Selector,\n // Always return false to allow re-renders when the selected value changes\n // The memoization is just to prevent re-renders when parent re-renders\n () => false\n );\n }\n}\n"],"mappings":";;;;;;;CA2CS,SAAS,OAKd,WACA,WACA;EACA,MAAM,cAAcA,uCAAkB,WAAW,UAAU;EAC3D,MAAMC,YAA4D,EAChE,sBACA,eACI;GACJ,MAAM,WAAW,YAAYC,WAAS;AAEtC,OAAI,SACF,iCAAqBC,4BAAU,MAAM,SAAS,SAAS,CAAC;AAE1D,mCAAqBA,4BAAU,MAAM,OAAO,YAAY,GAAG,CAAC;;AAG9D,yBACE,gBAGM,MACP"}
1
+ {"version":3,"file":"selector.cjs","names":["createUseSelector","Selector: component<TResolvedStep, TSteps, TChosenSteps>","MultiStepFormLogger","selector","Fragment"],"sources":["../src/selector.tsx"],"sourcesContent":["import {\n AnyResolvedStep,\n Expand,\n HelperFnChosenSteps,\n HelperFnCtx,\n MultiStepFormLogger,\n StepNumbers,\n} from '@jfdevelops/multi-step-form-core';\nimport { createElement, useMemo } from 'react';\nimport { Fragment } from 'react/jsx-runtime';\nimport {\n createUseSelector,\n SelectorFn,\n type DebugOptions,\n} from './hooks/use-selector';\n\nexport namespace selector {\n export type props<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>,\n TSelected\n > = {\n selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, TSelected>;\n children?: (selected: TSelected) => React.ReactNode;\n /**\n * Optional debug options to customize logging behavior.\n */\n debug?: boolean | DebugOptions<TSelected>;\n };\n export type component<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n > =\n /**\n * A component for reactively displaying a value from the form context.\n * Unlike `useSelector`, this component only re-renders itself, not the parent component.\n * Use this when you want to display a reactive value without causing parent re-renders.\n *\n * @param props - The props for the Selector component.\n *\n * @example\n * ```tsx\n * // With no debug options (default behavior)\n * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue}>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n\n * @example\n * ```tsx\n * // With debug enabled\n * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue} debug>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n *\n * @example\n * ```tsx\n * // With custom debug options\n * <Selector\n * selector={(ctx) => ctx.step1.fields.firstName.defaultValue}\n * debug={{\n * onRender: (value) => console.log('Rendered with:', value),\n * onValueChanged: (oldVal, newVal) => console.log('Changed:', oldVal, '->', newVal)\n * }}\n * >\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n */\n <TSelected>(\n props: props<TResolvedStep, TSteps, TChosenSteps, TSelected>\n ) => React.ReactNode;\n\n export function create<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n >(\n createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>,\n subscribe: (listener: () => void) => () => void\n ) {\n const useSelector = createUseSelector(createCtx, subscribe);\n const Selector: component<TResolvedStep, TSteps, TChosenSteps> = ({\n selector,\n children,\n debug,\n }) => {\n const isDebugEnabled =\n typeof debug === 'boolean' ? debug : debug !== undefined;\n const debugOptions = typeof debug === 'object' ? debug : undefined;\n const prefix = debugOptions?.prefix;\n\n const logger = useMemo(() => {\n if (!isDebugEnabled) {\n return undefined;\n }\n\n return new MultiStepFormLogger({\n debug: true,\n prefix: (defaultValue) => {\n const defaultPrefix = `${defaultValue}-Selector`;\n console.log(prefix);\n\n // If no prefix is provided, use default\n if (!prefix) {\n return defaultPrefix;\n }\n\n // If prefix is a string, use it directly\n if (typeof prefix === 'string') {\n return prefix;\n }\n\n // If prefix is a function, call it with the default prefix\n if (typeof prefix === 'function') {\n return prefix(defaultValue);\n }\n\n // If prefix is an object with action/value, handle prepend/append\n if (typeof prefix === 'object') {\n const { action = 'prepend', value, delimiter = '-' } = prefix;\n const resolvedValue =\n typeof value === 'function' ? value(defaultValue) : value;\n\n if (action === 'prepend') {\n return `${resolvedValue}${delimiter}${defaultPrefix}`;\n }\n\n if (action === 'append') {\n return `${defaultPrefix}${delimiter}${resolvedValue}`;\n }\n\n return defaultPrefix;\n }\n\n return defaultPrefix;\n },\n });\n }, [isDebugEnabled, prefix]);\n\n const selected = useSelector(selector, logger, debugOptions);\n\n logger?.info(\n debugOptions?.onRender?.(selected) ??\n `Rendering with selected: ${JSON.stringify(selected)}`\n );\n\n if (children) {\n return createElement(Fragment, null, children(selected));\n }\n return createElement(Fragment, null, String(selected ?? ''));\n };\n\n // Remove memo to test if it's causing the issue\n // The memo was meant to prevent re-renders when parent re-renders,\n // but useSyncExternalStore should handle reactivity\n return Selector;\n }\n}\n"],"mappings":";;;;;;;;CA4ES,SAAS,OAKd,WACA,WACA;EACA,MAAM,cAAcA,uCAAkB,WAAW,UAAU;EAC3D,MAAMC,YAA4D,EAChE,sBACA,UACA,YACI;GACJ,MAAM,iBACJ,OAAO,UAAU,YAAY,QAAQ,UAAU;GACjD,MAAM,eAAe,OAAO,UAAU,WAAW,QAAQ;GACzD,MAAM,SAAS,cAAc;GAE7B,MAAM,kCAAuB;AAC3B,QAAI,CAAC,eACH;AAGF,WAAO,IAAIC,sDAAoB;KAC7B,OAAO;KACP,SAAS,iBAAiB;MACxB,MAAM,gBAAgB,GAAG,aAAa;AACtC,cAAQ,IAAI,OAAO;AAGnB,UAAI,CAAC,OACH,QAAO;AAIT,UAAI,OAAO,WAAW,SACpB,QAAO;AAIT,UAAI,OAAO,WAAW,WACpB,QAAO,OAAO,aAAa;AAI7B,UAAI,OAAO,WAAW,UAAU;OAC9B,MAAM,EAAE,SAAS,WAAW,OAAO,YAAY,QAAQ;OACvD,MAAM,gBACJ,OAAO,UAAU,aAAa,MAAM,aAAa,GAAG;AAEtD,WAAI,WAAW,UACb,QAAO,GAAG,gBAAgB,YAAY;AAGxC,WAAI,WAAW,SACb,QAAO,GAAG,gBAAgB,YAAY;AAGxC,cAAO;;AAGT,aAAO;;KAEV,CAAC;MACD,CAAC,gBAAgB,OAAO,CAAC;GAE5B,MAAM,WAAW,YAAYC,YAAU,QAAQ,aAAa;AAE5D,WAAQ,KACN,cAAc,WAAW,SAAS,IAChC,4BAA4B,KAAK,UAAU,SAAS,GACvD;AAED,OAAI,SACF,iCAAqBC,4BAAU,MAAM,SAAS,SAAS,CAAC;AAE1D,mCAAqBA,4BAAU,MAAM,OAAO,YAAY,GAAG,CAAC;;AAM9D,SAAO"}
@@ -1,12 +1,15 @@
1
- import { SelectorFn } from "./hooks/use-selector.cjs";
1
+ import { DebugOptions, SelectorFn } from "./hooks/use-selector.cjs";
2
2
  import { AnyResolvedStep, Expand, HelperFnChosenSteps, HelperFnCtx, StepNumbers } from "@jfdevelops/multi-step-form-core";
3
- import * as react0 from "react";
4
3
 
5
4
  //#region src/selector.d.ts
6
5
  declare namespace selector {
7
6
  type props<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>, TSelected> = {
8
7
  selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, TSelected>;
9
8
  children?: (selected: TSelected) => React.ReactNode;
9
+ /**
10
+ * Optional debug options to customize logging behavior.
11
+ */
12
+ debug?: boolean | DebugOptions<TSelected>;
10
13
  };
11
14
  type component<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>> =
12
15
  /**
@@ -14,16 +17,39 @@ declare namespace selector {
14
17
  * Unlike `useSelector`, this component only re-renders itself, not the parent component.
15
18
  * Use this when you want to display a reactive value without causing parent re-renders.
16
19
  *
17
- * @param selector - A function that receives the current step's context and returns the selected value
18
- * @param children - Optional render prop that receives the selected value
20
+ * @param props - The props for the Selector component.
19
21
  *
20
22
  * @example
23
+ * ```tsx
24
+ * // With no debug options (default behavior)
21
25
  * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue}>
22
26
  * {(value) => <p>First name: {value}</p>}
23
27
  * </Selector>
28
+ * ```
29
+ * @example
30
+ * ```tsx
31
+ * // With debug enabled
32
+ * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue} debug>
33
+ * {(value) => <p>First name: {value}</p>}
34
+ * </Selector>
35
+ * ```
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // With custom debug options
40
+ * <Selector
41
+ * selector={(ctx) => ctx.step1.fields.firstName.defaultValue}
42
+ * debug={{
43
+ * onRender: (value) => console.log('Rendered with:', value),
44
+ * onValueChanged: (oldVal, newVal) => console.log('Changed:', oldVal, '->', newVal)
45
+ * }}
46
+ * >
47
+ * {(value) => <p>First name: {value}</p>}
48
+ * </Selector>
49
+ * ```
24
50
  */
25
51
  <TSelected>(props: props<TResolvedStep, TSteps, TChosenSteps, TSelected>) => React.ReactNode;
26
- function create<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>>(createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>, subscribe: (listener: () => void) => () => void): react0.MemoExoticComponent<component<TResolvedStep, TSteps, TChosenSteps>>;
52
+ function create<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>>(createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>, subscribe: (listener: () => void) => () => void): component<TResolvedStep, TSteps, TChosenSteps>;
27
53
  }
28
54
  //#endregion
29
55
  export { selector };
@@ -1,12 +1,15 @@
1
- import { SelectorFn } from "./hooks/use-selector.mjs";
1
+ import { DebugOptions, SelectorFn } from "./hooks/use-selector.mjs";
2
2
  import { AnyResolvedStep, Expand, HelperFnChosenSteps, HelperFnCtx, StepNumbers } from "@jfdevelops/multi-step-form-core";
3
- import * as react0 from "react";
4
3
 
5
4
  //#region src/selector.d.ts
6
5
  declare namespace selector {
7
6
  type props<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>, TSelected> = {
8
7
  selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, TSelected>;
9
8
  children?: (selected: TSelected) => React.ReactNode;
9
+ /**
10
+ * Optional debug options to customize logging behavior.
11
+ */
12
+ debug?: boolean | DebugOptions<TSelected>;
10
13
  };
11
14
  type component<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>> =
12
15
  /**
@@ -14,16 +17,39 @@ declare namespace selector {
14
17
  * Unlike `useSelector`, this component only re-renders itself, not the parent component.
15
18
  * Use this when you want to display a reactive value without causing parent re-renders.
16
19
  *
17
- * @param selector - A function that receives the current step's context and returns the selected value
18
- * @param children - Optional render prop that receives the selected value
20
+ * @param props - The props for the Selector component.
19
21
  *
20
22
  * @example
23
+ * ```tsx
24
+ * // With no debug options (default behavior)
21
25
  * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue}>
22
26
  * {(value) => <p>First name: {value}</p>}
23
27
  * </Selector>
28
+ * ```
29
+ * @example
30
+ * ```tsx
31
+ * // With debug enabled
32
+ * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue} debug>
33
+ * {(value) => <p>First name: {value}</p>}
34
+ * </Selector>
35
+ * ```
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // With custom debug options
40
+ * <Selector
41
+ * selector={(ctx) => ctx.step1.fields.firstName.defaultValue}
42
+ * debug={{
43
+ * onRender: (value) => console.log('Rendered with:', value),
44
+ * onValueChanged: (oldVal, newVal) => console.log('Changed:', oldVal, '->', newVal)
45
+ * }}
46
+ * >
47
+ * {(value) => <p>First name: {value}</p>}
48
+ * </Selector>
49
+ * ```
24
50
  */
25
51
  <TSelected>(props: props<TResolvedStep, TSteps, TChosenSteps, TSelected>) => React.ReactNode;
26
- function create<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>>(createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>, subscribe: (listener: () => void) => () => void): react0.MemoExoticComponent<component<TResolvedStep, TSteps, TChosenSteps>>;
52
+ function create<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>>(createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>, subscribe: (listener: () => void) => () => void): component<TResolvedStep, TSteps, TChosenSteps>;
27
53
  }
28
54
  //#endregion
29
55
  export { selector };
package/dist/selector.mjs CHANGED
@@ -1,18 +1,44 @@
1
1
  import { createUseSelector } from "./hooks/use-selector.mjs";
2
- import { createElement, memo } from "react";
2
+ import { MultiStepFormLogger } from "@jfdevelops/multi-step-form-core";
3
+ import { createElement, useMemo } from "react";
3
4
  import { Fragment } from "react/jsx-runtime";
4
5
 
5
6
  //#region src/selector.tsx
6
7
  let selector;
7
8
  (function(_selector) {
8
- function create(createCtx, subscribe) {
9
- const useSelector = createUseSelector(createCtx, subscribe);
10
- const Selector = ({ selector: selector$1, children }) => {
11
- const selected = useSelector(selector$1);
9
+ function create(createCtx$1, subscribe) {
10
+ const useSelector = createUseSelector(createCtx$1, subscribe);
11
+ const Selector = ({ selector: selector$1, children, debug }) => {
12
+ const isDebugEnabled = typeof debug === "boolean" ? debug : debug !== void 0;
13
+ const debugOptions = typeof debug === "object" ? debug : void 0;
14
+ const prefix = debugOptions?.prefix;
15
+ const logger = useMemo(() => {
16
+ if (!isDebugEnabled) return;
17
+ return new MultiStepFormLogger({
18
+ debug: true,
19
+ prefix: (defaultValue) => {
20
+ const defaultPrefix = `${defaultValue}-Selector`;
21
+ console.log(prefix);
22
+ if (!prefix) return defaultPrefix;
23
+ if (typeof prefix === "string") return prefix;
24
+ if (typeof prefix === "function") return prefix(defaultValue);
25
+ if (typeof prefix === "object") {
26
+ const { action = "prepend", value, delimiter = "-" } = prefix;
27
+ const resolvedValue = typeof value === "function" ? value(defaultValue) : value;
28
+ if (action === "prepend") return `${resolvedValue}${delimiter}${defaultPrefix}`;
29
+ if (action === "append") return `${defaultPrefix}${delimiter}${resolvedValue}`;
30
+ return defaultPrefix;
31
+ }
32
+ return defaultPrefix;
33
+ }
34
+ });
35
+ }, [isDebugEnabled, prefix]);
36
+ const selected = useSelector(selector$1, logger, debugOptions);
37
+ logger?.info(debugOptions?.onRender?.(selected) ?? `Rendering with selected: ${JSON.stringify(selected)}`);
12
38
  if (children) return createElement(Fragment, null, children(selected));
13
39
  return createElement(Fragment, null, String(selected ?? ""));
14
40
  };
15
- return memo(Selector, () => false);
41
+ return Selector;
16
42
  }
17
43
  _selector.create = create;
18
44
  })(selector || (selector = {}));
@@ -1 +1 @@
1
- {"version":3,"file":"selector.mjs","names":["Selector: component<TResolvedStep, TSteps, TChosenSteps>","selector"],"sources":["../src/selector.tsx"],"sourcesContent":["import {\n AnyResolvedStep,\n StepNumbers,\n HelperFnChosenSteps,\n Expand,\n HelperFnCtx,\n} from '@jfdevelops/multi-step-form-core';\nimport { createUseSelector, SelectorFn } from './hooks/use-selector';\nimport { Fragment } from 'react/jsx-runtime';\nimport { createElement, memo } from 'react';\n\nexport namespace selector {\n export type props<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>,\n TSelected\n > = {\n selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, TSelected>;\n children?: (selected: TSelected) => React.ReactNode;\n };\n export type component<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n > =\n /**\n * A component for reactively displaying a value from the form context.\n * Unlike `useSelector`, this component only re-renders itself, not the parent component.\n * Use this when you want to display a reactive value without causing parent re-renders.\n *\n * @param selector - A function that receives the current step's context and returns the selected value\n * @param children - Optional render prop that receives the selected value\n *\n * @example\n * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue}>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n */\n <TSelected>(\n props: props<TResolvedStep, TSteps, TChosenSteps, TSelected>\n ) => React.ReactNode;\n\n export function create<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n >(\n createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>,\n subscribe: (listener: () => void) => () => void\n ) {\n const useSelector = createUseSelector(createCtx, subscribe);\n const Selector: component<TResolvedStep, TSteps, TChosenSteps> = ({\n selector,\n children,\n }) => {\n const selected = useSelector(selector);\n\n if (children) {\n return createElement(Fragment, null, children(selected));\n }\n return createElement(Fragment, null, String(selected ?? ''));\n };\n\n return memo(\n Selector,\n // Always return false to allow re-renders when the selected value changes\n // The memoization is just to prevent re-renders when parent re-renders\n () => false\n );\n }\n}\n"],"mappings":";;;;;;;CA2CS,SAAS,OAKd,WACA,WACA;EACA,MAAM,cAAc,kBAAkB,WAAW,UAAU;EAC3D,MAAMA,YAA4D,EAChE,sBACA,eACI;GACJ,MAAM,WAAW,YAAYC,WAAS;AAEtC,OAAI,SACF,QAAO,cAAc,UAAU,MAAM,SAAS,SAAS,CAAC;AAE1D,UAAO,cAAc,UAAU,MAAM,OAAO,YAAY,GAAG,CAAC;;AAG9D,SAAO,KACL,gBAGM,MACP"}
1
+ {"version":3,"file":"selector.mjs","names":["createCtx","Selector: component<TResolvedStep, TSteps, TChosenSteps>","selector"],"sources":["../src/selector.tsx"],"sourcesContent":["import {\n AnyResolvedStep,\n Expand,\n HelperFnChosenSteps,\n HelperFnCtx,\n MultiStepFormLogger,\n StepNumbers,\n} from '@jfdevelops/multi-step-form-core';\nimport { createElement, useMemo } from 'react';\nimport { Fragment } from 'react/jsx-runtime';\nimport {\n createUseSelector,\n SelectorFn,\n type DebugOptions,\n} from './hooks/use-selector';\n\nexport namespace selector {\n export type props<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>,\n TSelected\n > = {\n selector: SelectorFn<TResolvedStep, TSteps, TChosenSteps, TSelected>;\n children?: (selected: TSelected) => React.ReactNode;\n /**\n * Optional debug options to customize logging behavior.\n */\n debug?: boolean | DebugOptions<TSelected>;\n };\n export type component<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n > =\n /**\n * A component for reactively displaying a value from the form context.\n * Unlike `useSelector`, this component only re-renders itself, not the parent component.\n * Use this when you want to display a reactive value without causing parent re-renders.\n *\n * @param props - The props for the Selector component.\n *\n * @example\n * ```tsx\n * // With no debug options (default behavior)\n * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue}>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n\n * @example\n * ```tsx\n * // With debug enabled\n * <Selector selector={(ctx) => ctx.step1.fields.firstName.defaultValue} debug>\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n *\n * @example\n * ```tsx\n * // With custom debug options\n * <Selector\n * selector={(ctx) => ctx.step1.fields.firstName.defaultValue}\n * debug={{\n * onRender: (value) => console.log('Rendered with:', value),\n * onValueChanged: (oldVal, newVal) => console.log('Changed:', oldVal, '->', newVal)\n * }}\n * >\n * {(value) => <p>First name: {value}</p>}\n * </Selector>\n * ```\n */\n <TSelected>(\n props: props<TResolvedStep, TSteps, TChosenSteps, TSelected>\n ) => React.ReactNode;\n\n export function create<\n TResolvedStep extends AnyResolvedStep,\n TSteps extends StepNumbers<TResolvedStep>,\n TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>\n >(\n createCtx: () => Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>,\n subscribe: (listener: () => void) => () => void\n ) {\n const useSelector = createUseSelector(createCtx, subscribe);\n const Selector: component<TResolvedStep, TSteps, TChosenSteps> = ({\n selector,\n children,\n debug,\n }) => {\n const isDebugEnabled =\n typeof debug === 'boolean' ? debug : debug !== undefined;\n const debugOptions = typeof debug === 'object' ? debug : undefined;\n const prefix = debugOptions?.prefix;\n\n const logger = useMemo(() => {\n if (!isDebugEnabled) {\n return undefined;\n }\n\n return new MultiStepFormLogger({\n debug: true,\n prefix: (defaultValue) => {\n const defaultPrefix = `${defaultValue}-Selector`;\n console.log(prefix);\n\n // If no prefix is provided, use default\n if (!prefix) {\n return defaultPrefix;\n }\n\n // If prefix is a string, use it directly\n if (typeof prefix === 'string') {\n return prefix;\n }\n\n // If prefix is a function, call it with the default prefix\n if (typeof prefix === 'function') {\n return prefix(defaultValue);\n }\n\n // If prefix is an object with action/value, handle prepend/append\n if (typeof prefix === 'object') {\n const { action = 'prepend', value, delimiter = '-' } = prefix;\n const resolvedValue =\n typeof value === 'function' ? value(defaultValue) : value;\n\n if (action === 'prepend') {\n return `${resolvedValue}${delimiter}${defaultPrefix}`;\n }\n\n if (action === 'append') {\n return `${defaultPrefix}${delimiter}${resolvedValue}`;\n }\n\n return defaultPrefix;\n }\n\n return defaultPrefix;\n },\n });\n }, [isDebugEnabled, prefix]);\n\n const selected = useSelector(selector, logger, debugOptions);\n\n logger?.info(\n debugOptions?.onRender?.(selected) ??\n `Rendering with selected: ${JSON.stringify(selected)}`\n );\n\n if (children) {\n return createElement(Fragment, null, children(selected));\n }\n return createElement(Fragment, null, String(selected ?? ''));\n };\n\n // Remove memo to test if it's causing the issue\n // The memo was meant to prevent re-renders when parent re-renders,\n // but useSyncExternalStore should handle reactivity\n return Selector;\n }\n}\n"],"mappings":";;;;;;;;CA4ES,SAAS,OAKd,aACA,WACA;EACA,MAAM,cAAc,kBAAkBA,aAAW,UAAU;EAC3D,MAAMC,YAA4D,EAChE,sBACA,UACA,YACI;GACJ,MAAM,iBACJ,OAAO,UAAU,YAAY,QAAQ,UAAU;GACjD,MAAM,eAAe,OAAO,UAAU,WAAW,QAAQ;GACzD,MAAM,SAAS,cAAc;GAE7B,MAAM,SAAS,cAAc;AAC3B,QAAI,CAAC,eACH;AAGF,WAAO,IAAI,oBAAoB;KAC7B,OAAO;KACP,SAAS,iBAAiB;MACxB,MAAM,gBAAgB,GAAG,aAAa;AACtC,cAAQ,IAAI,OAAO;AAGnB,UAAI,CAAC,OACH,QAAO;AAIT,UAAI,OAAO,WAAW,SACpB,QAAO;AAIT,UAAI,OAAO,WAAW,WACpB,QAAO,OAAO,aAAa;AAI7B,UAAI,OAAO,WAAW,UAAU;OAC9B,MAAM,EAAE,SAAS,WAAW,OAAO,YAAY,QAAQ;OACvD,MAAM,gBACJ,OAAO,UAAU,aAAa,MAAM,aAAa,GAAG;AAEtD,WAAI,WAAW,UACb,QAAO,GAAG,gBAAgB,YAAY;AAGxC,WAAI,WAAW,SACb,QAAO,GAAG,gBAAgB,YAAY;AAGxC,cAAO;;AAGT,aAAO;;KAEV,CAAC;MACD,CAAC,gBAAgB,OAAO,CAAC;GAE5B,MAAM,WAAW,YAAYC,YAAU,QAAQ,aAAa;AAE5D,WAAQ,KACN,cAAc,WAAW,SAAS,IAChC,4BAA4B,KAAK,UAAU,SAAS,GACvD;AAED,OAAI,SACF,QAAO,cAAc,UAAU,MAAM,SAAS,SAAS,CAAC;AAE1D,UAAO,cAAc,UAAU,MAAM,OAAO,YAAY,GAAG,CAAC;;AAM9D,SAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jfdevelops/react-multi-step-form",
3
- "version": "1.0.0-alpha.29",
3
+ "version": "1.0.0-alpha.30",
4
4
  "description": "",
5
5
  "repository": "https://github.com/jfdevelops/multi-step-form",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "use-sync-external-store": "^1.6.0",
39
- "@jfdevelops/multi-step-form-core": "1.0.0-alpha.20"
39
+ "@jfdevelops/multi-step-form-core": "1.0.0-alpha.21"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public",