@jfdevelops/react-multi-step-form 1.0.0-alpha.3 → 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.
- package/dist/create-context.cjs +92 -0
- package/dist/create-context.cjs.map +1 -0
- package/dist/create-context.d.cts +154 -0
- package/dist/create-context.d.mts +154 -0
- package/dist/create-context.mjs +92 -0
- package/dist/create-context.mjs.map +1 -0
- package/dist/field.cjs +41 -0
- package/dist/field.cjs.map +1 -0
- package/dist/field.d.cts +68 -0
- package/dist/field.d.mts +68 -0
- package/dist/field.mjs +36 -0
- package/dist/field.mjs.map +1 -0
- package/dist/form-config.cjs +32 -0
- package/dist/form-config.cjs.map +1 -0
- package/dist/form-config.d.cts +160 -0
- package/dist/form-config.d.mts +160 -0
- package/dist/form-config.mjs +27 -0
- package/dist/form-config.mjs.map +1 -0
- package/dist/hooks/use-multi-step-form-data.cjs +41 -0
- package/dist/hooks/use-multi-step-form-data.cjs.map +1 -0
- package/dist/hooks/use-multi-step-form-data.d.cts +28 -0
- package/dist/hooks/use-multi-step-form-data.d.mts +28 -0
- package/dist/hooks/use-multi-step-form-data.mjs +39 -0
- package/dist/hooks/use-multi-step-form-data.mjs.map +1 -0
- package/dist/hooks/use-selector.cjs +64 -0
- package/dist/hooks/use-selector.cjs.map +1 -0
- package/dist/hooks/use-selector.d.cts +92 -0
- package/dist/hooks/use-selector.d.mts +92 -0
- package/dist/hooks/use-selector.mjs +64 -0
- package/dist/hooks/use-selector.mjs.map +1 -0
- package/dist/index.cjs +16 -43
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +5 -1415
- package/dist/schema.cjs +45 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +34 -0
- package/dist/schema.d.mts +34 -0
- package/dist/schema.mjs +44 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/selector.cjs +53 -0
- package/dist/selector.cjs.map +1 -0
- package/dist/selector.d.cts +56 -0
- package/dist/selector.d.mts +56 -0
- package/dist/selector.mjs +48 -0
- package/dist/selector.mjs.map +1 -0
- package/dist/step-schema.cjs +244 -0
- package/dist/step-schema.cjs.map +1 -0
- package/dist/step-schema.d.cts +164 -0
- package/dist/step-schema.d.mts +164 -0
- package/dist/step-schema.mjs +237 -0
- package/dist/step-schema.mjs.map +1 -0
- package/dist/utils.cjs +34 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.mjs +33 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +15 -11
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/types/create-context.d.ts +0 -156
- package/dist/types/field.d.ts +0 -22
- package/dist/types/form-config.d.ts +0 -161
- package/dist/types/hooks/use-multi-step-form-data.d.ts +0 -26
- package/dist/types/index.d.ts +0 -4
- package/dist/types/schema.d.ts +0 -29
- package/dist/types/step-schema.d.ts +0 -134
- package/dist/types/utils.d.ts +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
let __jfdevelops_multi_step_form_core = require("@jfdevelops/multi-step-form-core");
|
|
2
|
+
let use_sync_external_store_shim_with_selector = require("use-sync-external-store/shim/with-selector");
|
|
3
|
+
|
|
4
|
+
//#region src/hooks/use-multi-step-form-data.ts
|
|
5
|
+
function throwIfInvalidStepNumber(schema, targetStep) {
|
|
6
|
+
(0, __jfdevelops_multi_step_form_core.invariant)(typeof targetStep === "string", `The target step must be a string, was ${typeof targetStep}`);
|
|
7
|
+
const { as, isValidStepNumber } = schema.stepSchema.steps;
|
|
8
|
+
const formattedStepNumbersList = new Intl.ListFormat("en", {
|
|
9
|
+
type: "disjunction",
|
|
10
|
+
style: "long"
|
|
11
|
+
}).format(as("array.string.untyped"));
|
|
12
|
+
(0, __jfdevelops_multi_step_form_core.invariant)(__jfdevelops_multi_step_form_core.VALIDATED_STEP_REGEX.test(targetStep), `The target step must match the following format: "step{number}". Available steps are ${formattedStepNumbersList}`);
|
|
13
|
+
const stepNumber = Number.parseInt(targetStep.replace("step", ""));
|
|
14
|
+
(0, __jfdevelops_multi_step_form_core.invariant)(isValidStepNumber(stepNumber), `The step number "${stepNumber}" is not a valid step number. Valid step numbers include ${formattedStepNumbersList}`, TypeError);
|
|
15
|
+
return stepNumber;
|
|
16
|
+
}
|
|
17
|
+
function createMultiStepFormDataHook(schema) {
|
|
18
|
+
function useMultiStepFormData$1(optionsOrSelector) {
|
|
19
|
+
return (0, use_sync_external_store_shim_with_selector.useSyncExternalStoreWithSelector)(schema.subscribe, () => schema.getSnapshot(), () => schema.getSnapshot(), (snapshot) => {
|
|
20
|
+
if (typeof optionsOrSelector === "object") {
|
|
21
|
+
const stepNumber = throwIfInvalidStepNumber(snapshot, optionsOrSelector.targetStep);
|
|
22
|
+
return snapshot.stepSchema.get({ step: stepNumber }).data;
|
|
23
|
+
}
|
|
24
|
+
if (typeof optionsOrSelector === "function") return optionsOrSelector(snapshot);
|
|
25
|
+
return snapshot;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return useMultiStepFormData$1;
|
|
29
|
+
}
|
|
30
|
+
function useMultiStepFormData(schema, optionsOrSelector) {
|
|
31
|
+
const hook = createMultiStepFormDataHook(schema);
|
|
32
|
+
if (typeof optionsOrSelector === "object") return hook(optionsOrSelector);
|
|
33
|
+
if (typeof optionsOrSelector === "function") return hook(optionsOrSelector);
|
|
34
|
+
return hook();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
exports.createMultiStepFormDataHook = createMultiStepFormDataHook;
|
|
39
|
+
exports.throwIfInvalidStepNumber = throwIfInvalidStepNumber;
|
|
40
|
+
exports.useMultiStepFormData = useMultiStepFormData;
|
|
41
|
+
//# sourceMappingURL=use-multi-step-form-data.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-multi-step-form-data.cjs","names":["VALIDATED_STEP_REGEX","useMultiStepFormData"],"sources":["../../src/hooks/use-multi-step-form-data.ts"],"sourcesContent":["import type { AnyMultiStepFormSchema, MultiStepFormSchema } from '@/schema';\nimport {\n invariant,\n VALIDATED_STEP_REGEX,\n} from '@jfdevelops/multi-step-form-core';\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';\n\nexport type UseMultiStepFormDataOptions<\n TSchema extends AnyMultiStepFormSchema,\n TTarget extends keyof MultiStepFormSchema.resolvedStep<TSchema> = keyof MultiStepFormSchema.resolvedStep<TSchema>\n> = {\n targetStep: TTarget;\n};\nexport type UseMultiStepFormData<\n // step extends Step<casing>,\n // casing extends CasingType,\n // storageKey extends string,\n // resolvedStep extends ResolvedStep<step, casing>,\n // schema extends MultiStepFormSchema<\n // step,\n // casing,\n // storageKey\n // > = MultiStepFormSchema<step, casing, storageKey>,\n TSchema extends AnyMultiStepFormSchema,\n TResolvedStep extends MultiStepFormSchema.resolvedStep<TSchema> = MultiStepFormSchema.resolvedStep<TSchema>\n> = {\n /**\n * Returns the entire {@linkcode MultiStepFormSchema instance}.\n */\n (): TSchema;\n /**\n * Returns the data for the target step.\n * @param stepNumber The step number to return.\n * @throws {TypeError} If `options.stepNumber` is invalid.\n */\n <targetStep extends keyof TResolvedStep>(\n options: UseMultiStepFormDataOptions<TSchema, targetStep>\n ): MultiStepFormSchema.getData<TSchema, targetStep>;\n /**\n * Returns the specified data from the {@linkcode MultiStepFormSchema} instance via the callback's return.\n */\n <data>(selector: (schema: TSchema) => data): data;\n};\n\nexport function throwIfInvalidStepNumber<\n // step extends Step<casing>,\n // casing extends CasingType = DefaultCasing,\n // storageKey extends string = DefaultStorageKey,\n // resolvedStep extends ResolvedStep<step, casing> = ResolvedStep<step, casing>,\n // stepNumbers extends StepNumbers<resolvedStep> = StepNumbers<resolvedStep>\n schema extends AnyMultiStepFormSchema\n>(schema: schema, targetStep: unknown) {\n invariant(\n typeof targetStep === 'string',\n `The target step must be a string, was ${typeof targetStep}`\n );\n\n const { as, isValidStepNumber } = schema.stepSchema.steps;\n const formatter = new Intl.ListFormat('en', {\n type: 'disjunction',\n style: 'long',\n });\n const formattedStepNumbersList = formatter.format(as('array.string.untyped'));\n\n invariant(\n VALIDATED_STEP_REGEX.test(targetStep),\n `The target step must match the following format: \"step{number}\". Available steps are ${formattedStepNumbersList}`\n );\n\n const stepNumber = Number.parseInt(targetStep.replace('step', ''));\n\n invariant(\n isValidStepNumber(stepNumber),\n `The step number \"${stepNumber}\" is not a valid step number. Valid step numbers include ${formattedStepNumbersList}`,\n TypeError\n );\n\n return stepNumber as MultiStepFormSchema.stepNumbers<schema>;\n}\n\nexport function createMultiStepFormDataHook<\n // step extends Step<casing>,\n // casing extends CasingType = DefaultCasing,\n // storageKey extends string = DefaultStorageKey,\n // resolvedStep extends ResolvedStep<step, casing> = ResolvedStep<step, casing>,\n // stepNumbers extends StepNumbers<resolvedStep> = StepNumbers<resolvedStep>\n schema extends AnyMultiStepFormSchema\n>(schema: schema): UseMultiStepFormData<schema> {\n function useMultiStepFormData(\n optionsOrSelector?:\n | UseMultiStepFormDataOptions<schema>\n | ((data: schema) => unknown)\n ) {\n return useSyncExternalStoreWithSelector(\n schema.subscribe,\n () => schema.getSnapshot(),\n () => schema.getSnapshot(),\n (snapshot) => {\n if (typeof optionsOrSelector === 'object') {\n // @ts-ignore Type instantiation is excessively deep and possibly infinite\n const stepNumber = throwIfInvalidStepNumber(\n snapshot,\n optionsOrSelector.targetStep\n );\n\n return snapshot.stepSchema.get({ step: stepNumber as never }).data;\n }\n\n if (typeof optionsOrSelector === 'function') {\n return optionsOrSelector(snapshot);\n }\n\n return snapshot;\n }\n );\n }\n\n return useMultiStepFormData as any;\n}\n\nfunction useMultiStepFormData<schema extends AnyMultiStepFormSchema>(\n schema: schema\n): schema;\nfunction useMultiStepFormData<\n schema extends AnyMultiStepFormSchema,\n targetStep extends keyof MultiStepFormSchema.resolvedStep<schema>\n>(\n schema: schema,\n options: UseMultiStepFormDataOptions<schema, targetStep>\n): MultiStepFormSchema.getData<schema, targetStep>;\nfunction useMultiStepFormData<schema extends AnyMultiStepFormSchema, data>(\n schema: schema,\n selector: (schema: schema) => data\n): data;\nfunction useMultiStepFormData<\n // step extends Step<casing>,\n // casing extends CasingType = DefaultCasing,\n // storageKey extends string = DefaultStorageKey,\n // resolvedStep extends ResolvedStep<step, casing> = ResolvedStep<step, casing>,\n // stepNumbers extends StepNumbers<resolvedStep> = StepNumbers<resolvedStep>\n schema extends AnyMultiStepFormSchema\n>(\n schema: schema,\n optionsOrSelector?:\n | UseMultiStepFormDataOptions<schema>\n | ((data: schema) => unknown)\n) {\n const hook = createMultiStepFormDataHook(schema);\n\n if (typeof optionsOrSelector === 'object') {\n return hook(optionsOrSelector);\n }\n\n if (typeof optionsOrSelector === 'function') {\n return hook(optionsOrSelector);\n }\n\n return hook();\n}\n\nexport { useMultiStepFormData };\n"],"mappings":";;;;AA4CA,SAAgB,yBAOd,QAAgB,YAAqB;AACrC,kDACE,OAAO,eAAe,UACtB,yCAAyC,OAAO,aACjD;CAED,MAAM,EAAE,IAAI,sBAAsB,OAAO,WAAW;CAKpD,MAAM,2BAJY,IAAI,KAAK,WAAW,MAAM;EAC1C,MAAM;EACN,OAAO;EACR,CAAC,CACyC,OAAO,GAAG,uBAAuB,CAAC;AAE7E,kDACEA,uDAAqB,KAAK,WAAW,EACrC,wFAAwF,2BACzF;CAED,MAAM,aAAa,OAAO,SAAS,WAAW,QAAQ,QAAQ,GAAG,CAAC;AAElE,kDACE,kBAAkB,WAAW,EAC7B,oBAAoB,WAAW,2DAA2D,4BAC1F,UACD;AAED,QAAO;;AAGT,SAAgB,4BAOd,QAA8C;CAC9C,SAASC,uBACP,mBAGA;AACA,0FACE,OAAO,iBACD,OAAO,aAAa,QACpB,OAAO,aAAa,GACzB,aAAa;AACZ,OAAI,OAAO,sBAAsB,UAAU;IAEzC,MAAM,aAAa,yBACjB,UACA,kBAAkB,WACnB;AAED,WAAO,SAAS,WAAW,IAAI,EAAE,MAAM,YAAqB,CAAC,CAAC;;AAGhE,OAAI,OAAO,sBAAsB,WAC/B,QAAO,kBAAkB,SAAS;AAGpC,UAAO;IAEV;;AAGH,QAAOA;;AAiBT,SAAS,qBAQP,QACA,mBAGA;CACA,MAAM,OAAO,4BAA4B,OAAO;AAEhD,KAAI,OAAO,sBAAsB,SAC/B,QAAO,KAAK,kBAAkB;AAGhC,KAAI,OAAO,sBAAsB,WAC/B,QAAO,KAAK,kBAAkB;AAGhC,QAAO,MAAM"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AnyMultiStepFormSchema, MultiStepFormSchema } from "../schema.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/use-multi-step-form-data.d.ts
|
|
4
|
+
type UseMultiStepFormDataOptions<TSchema extends AnyMultiStepFormSchema, TTarget extends keyof MultiStepFormSchema.resolvedStep<TSchema> = keyof MultiStepFormSchema.resolvedStep<TSchema>> = {
|
|
5
|
+
targetStep: TTarget;
|
|
6
|
+
};
|
|
7
|
+
type UseMultiStepFormData<TSchema extends AnyMultiStepFormSchema, TResolvedStep extends MultiStepFormSchema.resolvedStep<TSchema> = MultiStepFormSchema.resolvedStep<TSchema>> = {
|
|
8
|
+
/**
|
|
9
|
+
* Returns the entire {@linkcode MultiStepFormSchema instance}.
|
|
10
|
+
*/
|
|
11
|
+
(): TSchema;
|
|
12
|
+
/**
|
|
13
|
+
* Returns the data for the target step.
|
|
14
|
+
* @param stepNumber The step number to return.
|
|
15
|
+
* @throws {TypeError} If `options.stepNumber` is invalid.
|
|
16
|
+
*/
|
|
17
|
+
<targetStep extends keyof TResolvedStep>(options: UseMultiStepFormDataOptions<TSchema, targetStep>): MultiStepFormSchema.getData<TSchema, targetStep>;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the specified data from the {@linkcode MultiStepFormSchema} instance via the callback's return.
|
|
20
|
+
*/
|
|
21
|
+
<data>(selector: (schema: TSchema) => data): data;
|
|
22
|
+
};
|
|
23
|
+
declare function useMultiStepFormData<schema extends AnyMultiStepFormSchema>(schema: schema): schema;
|
|
24
|
+
declare function useMultiStepFormData<schema extends AnyMultiStepFormSchema, targetStep extends keyof MultiStepFormSchema.resolvedStep<schema>>(schema: schema, options: UseMultiStepFormDataOptions<schema, targetStep>): MultiStepFormSchema.getData<schema, targetStep>;
|
|
25
|
+
declare function useMultiStepFormData<schema extends AnyMultiStepFormSchema, data>(schema: schema, selector: (schema: schema) => data): data;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { UseMultiStepFormData, useMultiStepFormData };
|
|
28
|
+
//# sourceMappingURL=use-multi-step-form-data.d.cts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AnyMultiStepFormSchema, MultiStepFormSchema } from "../schema.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/use-multi-step-form-data.d.ts
|
|
4
|
+
type UseMultiStepFormDataOptions<TSchema extends AnyMultiStepFormSchema, TTarget extends keyof MultiStepFormSchema.resolvedStep<TSchema> = keyof MultiStepFormSchema.resolvedStep<TSchema>> = {
|
|
5
|
+
targetStep: TTarget;
|
|
6
|
+
};
|
|
7
|
+
type UseMultiStepFormData<TSchema extends AnyMultiStepFormSchema, TResolvedStep extends MultiStepFormSchema.resolvedStep<TSchema> = MultiStepFormSchema.resolvedStep<TSchema>> = {
|
|
8
|
+
/**
|
|
9
|
+
* Returns the entire {@linkcode MultiStepFormSchema instance}.
|
|
10
|
+
*/
|
|
11
|
+
(): TSchema;
|
|
12
|
+
/**
|
|
13
|
+
* Returns the data for the target step.
|
|
14
|
+
* @param stepNumber The step number to return.
|
|
15
|
+
* @throws {TypeError} If `options.stepNumber` is invalid.
|
|
16
|
+
*/
|
|
17
|
+
<targetStep extends keyof TResolvedStep>(options: UseMultiStepFormDataOptions<TSchema, targetStep>): MultiStepFormSchema.getData<TSchema, targetStep>;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the specified data from the {@linkcode MultiStepFormSchema} instance via the callback's return.
|
|
20
|
+
*/
|
|
21
|
+
<data>(selector: (schema: TSchema) => data): data;
|
|
22
|
+
};
|
|
23
|
+
declare function useMultiStepFormData<schema extends AnyMultiStepFormSchema>(schema: schema): schema;
|
|
24
|
+
declare function useMultiStepFormData<schema extends AnyMultiStepFormSchema, targetStep extends keyof MultiStepFormSchema.resolvedStep<schema>>(schema: schema, options: UseMultiStepFormDataOptions<schema, targetStep>): MultiStepFormSchema.getData<schema, targetStep>;
|
|
25
|
+
declare function useMultiStepFormData<schema extends AnyMultiStepFormSchema, data>(schema: schema, selector: (schema: schema) => data): data;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { UseMultiStepFormData, useMultiStepFormData };
|
|
28
|
+
//# sourceMappingURL=use-multi-step-form-data.d.mts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { VALIDATED_STEP_REGEX, invariant } from "@jfdevelops/multi-step-form-core";
|
|
2
|
+
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector";
|
|
3
|
+
|
|
4
|
+
//#region src/hooks/use-multi-step-form-data.ts
|
|
5
|
+
function throwIfInvalidStepNumber(schema, targetStep) {
|
|
6
|
+
invariant(typeof targetStep === "string", `The target step must be a string, was ${typeof targetStep}`);
|
|
7
|
+
const { as, isValidStepNumber } = schema.stepSchema.steps;
|
|
8
|
+
const formattedStepNumbersList = new Intl.ListFormat("en", {
|
|
9
|
+
type: "disjunction",
|
|
10
|
+
style: "long"
|
|
11
|
+
}).format(as("array.string.untyped"));
|
|
12
|
+
invariant(VALIDATED_STEP_REGEX.test(targetStep), `The target step must match the following format: "step{number}". Available steps are ${formattedStepNumbersList}`);
|
|
13
|
+
const stepNumber = Number.parseInt(targetStep.replace("step", ""));
|
|
14
|
+
invariant(isValidStepNumber(stepNumber), `The step number "${stepNumber}" is not a valid step number. Valid step numbers include ${formattedStepNumbersList}`, TypeError);
|
|
15
|
+
return stepNumber;
|
|
16
|
+
}
|
|
17
|
+
function createMultiStepFormDataHook(schema) {
|
|
18
|
+
function useMultiStepFormData$1(optionsOrSelector) {
|
|
19
|
+
return useSyncExternalStoreWithSelector(schema.subscribe, () => schema.getSnapshot(), () => schema.getSnapshot(), (snapshot) => {
|
|
20
|
+
if (typeof optionsOrSelector === "object") {
|
|
21
|
+
const stepNumber = throwIfInvalidStepNumber(snapshot, optionsOrSelector.targetStep);
|
|
22
|
+
return snapshot.stepSchema.get({ step: stepNumber }).data;
|
|
23
|
+
}
|
|
24
|
+
if (typeof optionsOrSelector === "function") return optionsOrSelector(snapshot);
|
|
25
|
+
return snapshot;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return useMultiStepFormData$1;
|
|
29
|
+
}
|
|
30
|
+
function useMultiStepFormData(schema, optionsOrSelector) {
|
|
31
|
+
const hook = createMultiStepFormDataHook(schema);
|
|
32
|
+
if (typeof optionsOrSelector === "object") return hook(optionsOrSelector);
|
|
33
|
+
if (typeof optionsOrSelector === "function") return hook(optionsOrSelector);
|
|
34
|
+
return hook();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { createMultiStepFormDataHook, throwIfInvalidStepNumber, useMultiStepFormData };
|
|
39
|
+
//# sourceMappingURL=use-multi-step-form-data.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-multi-step-form-data.mjs","names":["useMultiStepFormData"],"sources":["../../src/hooks/use-multi-step-form-data.ts"],"sourcesContent":["import type { AnyMultiStepFormSchema, MultiStepFormSchema } from '@/schema';\nimport {\n invariant,\n VALIDATED_STEP_REGEX,\n} from '@jfdevelops/multi-step-form-core';\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';\n\nexport type UseMultiStepFormDataOptions<\n TSchema extends AnyMultiStepFormSchema,\n TTarget extends keyof MultiStepFormSchema.resolvedStep<TSchema> = keyof MultiStepFormSchema.resolvedStep<TSchema>\n> = {\n targetStep: TTarget;\n};\nexport type UseMultiStepFormData<\n // step extends Step<casing>,\n // casing extends CasingType,\n // storageKey extends string,\n // resolvedStep extends ResolvedStep<step, casing>,\n // schema extends MultiStepFormSchema<\n // step,\n // casing,\n // storageKey\n // > = MultiStepFormSchema<step, casing, storageKey>,\n TSchema extends AnyMultiStepFormSchema,\n TResolvedStep extends MultiStepFormSchema.resolvedStep<TSchema> = MultiStepFormSchema.resolvedStep<TSchema>\n> = {\n /**\n * Returns the entire {@linkcode MultiStepFormSchema instance}.\n */\n (): TSchema;\n /**\n * Returns the data for the target step.\n * @param stepNumber The step number to return.\n * @throws {TypeError} If `options.stepNumber` is invalid.\n */\n <targetStep extends keyof TResolvedStep>(\n options: UseMultiStepFormDataOptions<TSchema, targetStep>\n ): MultiStepFormSchema.getData<TSchema, targetStep>;\n /**\n * Returns the specified data from the {@linkcode MultiStepFormSchema} instance via the callback's return.\n */\n <data>(selector: (schema: TSchema) => data): data;\n};\n\nexport function throwIfInvalidStepNumber<\n // step extends Step<casing>,\n // casing extends CasingType = DefaultCasing,\n // storageKey extends string = DefaultStorageKey,\n // resolvedStep extends ResolvedStep<step, casing> = ResolvedStep<step, casing>,\n // stepNumbers extends StepNumbers<resolvedStep> = StepNumbers<resolvedStep>\n schema extends AnyMultiStepFormSchema\n>(schema: schema, targetStep: unknown) {\n invariant(\n typeof targetStep === 'string',\n `The target step must be a string, was ${typeof targetStep}`\n );\n\n const { as, isValidStepNumber } = schema.stepSchema.steps;\n const formatter = new Intl.ListFormat('en', {\n type: 'disjunction',\n style: 'long',\n });\n const formattedStepNumbersList = formatter.format(as('array.string.untyped'));\n\n invariant(\n VALIDATED_STEP_REGEX.test(targetStep),\n `The target step must match the following format: \"step{number}\". Available steps are ${formattedStepNumbersList}`\n );\n\n const stepNumber = Number.parseInt(targetStep.replace('step', ''));\n\n invariant(\n isValidStepNumber(stepNumber),\n `The step number \"${stepNumber}\" is not a valid step number. Valid step numbers include ${formattedStepNumbersList}`,\n TypeError\n );\n\n return stepNumber as MultiStepFormSchema.stepNumbers<schema>;\n}\n\nexport function createMultiStepFormDataHook<\n // step extends Step<casing>,\n // casing extends CasingType = DefaultCasing,\n // storageKey extends string = DefaultStorageKey,\n // resolvedStep extends ResolvedStep<step, casing> = ResolvedStep<step, casing>,\n // stepNumbers extends StepNumbers<resolvedStep> = StepNumbers<resolvedStep>\n schema extends AnyMultiStepFormSchema\n>(schema: schema): UseMultiStepFormData<schema> {\n function useMultiStepFormData(\n optionsOrSelector?:\n | UseMultiStepFormDataOptions<schema>\n | ((data: schema) => unknown)\n ) {\n return useSyncExternalStoreWithSelector(\n schema.subscribe,\n () => schema.getSnapshot(),\n () => schema.getSnapshot(),\n (snapshot) => {\n if (typeof optionsOrSelector === 'object') {\n // @ts-ignore Type instantiation is excessively deep and possibly infinite\n const stepNumber = throwIfInvalidStepNumber(\n snapshot,\n optionsOrSelector.targetStep\n );\n\n return snapshot.stepSchema.get({ step: stepNumber as never }).data;\n }\n\n if (typeof optionsOrSelector === 'function') {\n return optionsOrSelector(snapshot);\n }\n\n return snapshot;\n }\n );\n }\n\n return useMultiStepFormData as any;\n}\n\nfunction useMultiStepFormData<schema extends AnyMultiStepFormSchema>(\n schema: schema\n): schema;\nfunction useMultiStepFormData<\n schema extends AnyMultiStepFormSchema,\n targetStep extends keyof MultiStepFormSchema.resolvedStep<schema>\n>(\n schema: schema,\n options: UseMultiStepFormDataOptions<schema, targetStep>\n): MultiStepFormSchema.getData<schema, targetStep>;\nfunction useMultiStepFormData<schema extends AnyMultiStepFormSchema, data>(\n schema: schema,\n selector: (schema: schema) => data\n): data;\nfunction useMultiStepFormData<\n // step extends Step<casing>,\n // casing extends CasingType = DefaultCasing,\n // storageKey extends string = DefaultStorageKey,\n // resolvedStep extends ResolvedStep<step, casing> = ResolvedStep<step, casing>,\n // stepNumbers extends StepNumbers<resolvedStep> = StepNumbers<resolvedStep>\n schema extends AnyMultiStepFormSchema\n>(\n schema: schema,\n optionsOrSelector?:\n | UseMultiStepFormDataOptions<schema>\n | ((data: schema) => unknown)\n) {\n const hook = createMultiStepFormDataHook(schema);\n\n if (typeof optionsOrSelector === 'object') {\n return hook(optionsOrSelector);\n }\n\n if (typeof optionsOrSelector === 'function') {\n return hook(optionsOrSelector);\n }\n\n return hook();\n}\n\nexport { useMultiStepFormData };\n"],"mappings":";;;;AA4CA,SAAgB,yBAOd,QAAgB,YAAqB;AACrC,WACE,OAAO,eAAe,UACtB,yCAAyC,OAAO,aACjD;CAED,MAAM,EAAE,IAAI,sBAAsB,OAAO,WAAW;CAKpD,MAAM,2BAJY,IAAI,KAAK,WAAW,MAAM;EAC1C,MAAM;EACN,OAAO;EACR,CAAC,CACyC,OAAO,GAAG,uBAAuB,CAAC;AAE7E,WACE,qBAAqB,KAAK,WAAW,EACrC,wFAAwF,2BACzF;CAED,MAAM,aAAa,OAAO,SAAS,WAAW,QAAQ,QAAQ,GAAG,CAAC;AAElE,WACE,kBAAkB,WAAW,EAC7B,oBAAoB,WAAW,2DAA2D,4BAC1F,UACD;AAED,QAAO;;AAGT,SAAgB,4BAOd,QAA8C;CAC9C,SAASA,uBACP,mBAGA;AACA,SAAO,iCACL,OAAO,iBACD,OAAO,aAAa,QACpB,OAAO,aAAa,GACzB,aAAa;AACZ,OAAI,OAAO,sBAAsB,UAAU;IAEzC,MAAM,aAAa,yBACjB,UACA,kBAAkB,WACnB;AAED,WAAO,SAAS,WAAW,IAAI,EAAE,MAAM,YAAqB,CAAC,CAAC;;AAGhE,OAAI,OAAO,sBAAsB,WAC/B,QAAO,kBAAkB,SAAS;AAGpC,UAAO;IAEV;;AAGH,QAAOA;;AAiBT,SAAS,qBAQP,QACA,mBAGA;CACA,MAAM,OAAO,4BAA4B,OAAO;AAEhD,KAAI,OAAO,sBAAsB,SAC/B,QAAO,KAAK,kBAAkB;AAGhC,KAAI,OAAO,sBAAsB,WAC/B,QAAO,KAAK,kBAAkB;AAGhC,QAAO,MAAM"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
let react = require("react");
|
|
2
|
+
|
|
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
|
+
}
|
|
28
|
+
function createUseSelector(createCtx, subscribe) {
|
|
29
|
+
return (selectorFn, logger, debugOptions) => {
|
|
30
|
+
const snapshotCacheRef = (0, react.useRef)(null);
|
|
31
|
+
const selectorRef = (0, react.useRef)(selectorFn);
|
|
32
|
+
selectorRef.current = selectorFn;
|
|
33
|
+
const getSnapshot = () => {
|
|
34
|
+
const currentCtx = createCtx();
|
|
35
|
+
const newValue = selectorRef.current(currentCtx);
|
|
36
|
+
if (snapshotCacheRef.current === null) {
|
|
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)}`);
|
|
53
|
+
return newValue;
|
|
54
|
+
}
|
|
55
|
+
logger?.info(debugOptions?.onValueUnchanged?.(newValue) ?? `Value unchanged: ${JSON.stringify(newValue)}`);
|
|
56
|
+
return snapshotCacheRef.current.value;
|
|
57
|
+
};
|
|
58
|
+
return (0, react.useSyncExternalStore)(subscribe, () => getSnapshot(), () => getSnapshot());
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
exports.createUseSelector = createUseSelector;
|
|
64
|
+
//# sourceMappingURL=use-selector.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AnyResolvedStep, Expand, HelperFnChosenSteps, HelperFnCtx, MultiStepFormLogger, MultiStepFormLoggerOptions, StepNumbers } from "@jfdevelops/multi-step-form-core";
|
|
2
|
+
|
|
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
|
+
};
|
|
87
|
+
type UseSelector<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>> = ReturnType<typeof createUseSelector<TResolvedStep, TSteps, TChosenSteps>>;
|
|
88
|
+
type SelectorFn<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>, TSelected> = (ctx: Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>) => TSelected;
|
|
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;
|
|
90
|
+
//#endregion
|
|
91
|
+
export { DebugOptions, SelectorFn, UseSelector };
|
|
92
|
+
//# sourceMappingURL=use-selector.d.cts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AnyResolvedStep, Expand, HelperFnChosenSteps, HelperFnCtx, MultiStepFormLogger, MultiStepFormLoggerOptions, StepNumbers } from "@jfdevelops/multi-step-form-core";
|
|
2
|
+
|
|
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
|
+
};
|
|
87
|
+
type UseSelector<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>> = ReturnType<typeof createUseSelector<TResolvedStep, TSteps, TChosenSteps>>;
|
|
88
|
+
type SelectorFn<TResolvedStep extends AnyResolvedStep, TSteps extends StepNumbers<TResolvedStep>, TChosenSteps extends HelperFnChosenSteps<TResolvedStep, TSteps>, TSelected> = (ctx: Expand<HelperFnCtx<TResolvedStep, TSteps, TChosenSteps>>) => TSelected;
|
|
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;
|
|
90
|
+
//#endregion
|
|
91
|
+
export { DebugOptions, SelectorFn, UseSelector };
|
|
92
|
+
//# sourceMappingURL=use-selector.d.mts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
2
|
+
|
|
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
|
+
}
|
|
28
|
+
function createUseSelector(createCtx, subscribe) {
|
|
29
|
+
return (selectorFn, logger, debugOptions) => {
|
|
30
|
+
const snapshotCacheRef = useRef(null);
|
|
31
|
+
const selectorRef = useRef(selectorFn);
|
|
32
|
+
selectorRef.current = selectorFn;
|
|
33
|
+
const getSnapshot = () => {
|
|
34
|
+
const currentCtx = createCtx();
|
|
35
|
+
const newValue = selectorRef.current(currentCtx);
|
|
36
|
+
if (snapshotCacheRef.current === null) {
|
|
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)}`);
|
|
53
|
+
return newValue;
|
|
54
|
+
}
|
|
55
|
+
logger?.info(debugOptions?.onValueUnchanged?.(newValue) ?? `Value unchanged: ${JSON.stringify(newValue)}`);
|
|
56
|
+
return snapshotCacheRef.current.value;
|
|
57
|
+
};
|
|
58
|
+
return useSyncExternalStore(subscribe, () => getSnapshot(), () => getSnapshot());
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
export { createUseSelector };
|
|
64
|
+
//# sourceMappingURL=use-selector.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|