@lattice-php/lattice 0.32.0 → 0.33.0
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/appearance/index.d.ts +6 -0
- package/dist/appearance/index.js +11 -2
- package/dist/appearance/index.js.map +1 -1
- package/dist/create-app.d.ts +6 -3
- package/dist/create-app.js +13 -5
- package/dist/create-app.js.map +1 -1
- package/dist/form/components/fields/use-flip-reorder.js +3 -2
- package/dist/form/components/fields/use-flip-reorder.js.map +1 -1
- package/dist/form/components/fields/use-row-collection.js +3 -2
- package/dist/form/components/fields/use-row-collection.js.map +1 -1
- package/dist/form/hooks/use-form-resolver.js +3 -2
- package/dist/form/hooks/use-form-resolver.js.map +1 -1
- package/dist/i18n/instance.d.ts +8 -0
- package/dist/i18n/instance.js +17 -1
- package/dist/i18n/instance.js.map +1 -1
- package/dist/lib/use-layout-effect.d.ts +6 -0
- package/dist/lib/use-layout-effect.js +11 -0
- package/dist/lib/use-layout-effect.js.map +1 -0
- package/dist/ssr.d.ts +20 -0
- package/dist/ssr.js +32 -0
- package/dist/ssr.js.map +1 -0
- package/dist/vite.js +2 -5
- package/dist/vite.js.map +1 -1
- package/dist-standalone/chunks/{icon-button-CEwL9uXW.js → icon-button-D4DKcnhp.js} +1 -1
- package/dist-standalone/chunks/instance-BQeVkUpw.js +1 -0
- package/dist-standalone/chunks/page-props-BA5zqTIT.js +1 -0
- package/dist-standalone/chunks/{rich-editor-field-DVvEzA3W.js → rich-editor-field-BeeSr2Zx.js} +1 -1
- package/dist-standalone/chunks/{subscriptions-HzwKFUPT.js → subscriptions-Cwd1jFs5.js} +1 -1
- package/dist-standalone/lattice.css +1 -1
- package/dist-standalone/lattice.js +4 -4
- package/dist-standalone/manifest.json +7 -7
- package/package.json +9 -1
- package/dist-standalone/chunks/instance-B-C4sgf4.js +0 -1
- package/dist-standalone/chunks/page-props-nxR5n6k4.js +0 -1
|
@@ -6,6 +6,12 @@ export type UseAppearanceReturn = {
|
|
|
6
6
|
readonly resolvedAppearance: ResolvedAppearance;
|
|
7
7
|
readonly updateAppearance: (mode: Appearance) => void;
|
|
8
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* Seed the SSR/hydration snapshot from the backend-shared `lattice.appearance`
|
|
11
|
+
* prop. The server render and the client's hydration pass must read the same
|
|
12
|
+
* value, or components branching on the appearance mismatch on hydration.
|
|
13
|
+
*/
|
|
14
|
+
export declare function seedAppearance(value: unknown): void;
|
|
9
15
|
export declare function isAppearance(value: unknown): value is Appearance;
|
|
10
16
|
export declare function updateAppearance(mode: Appearance): void;
|
|
11
17
|
export declare function initializeAppearance(): void;
|
package/dist/appearance/index.js
CHANGED
|
@@ -7,6 +7,15 @@ var appearances = [
|
|
|
7
7
|
];
|
|
8
8
|
var listeners = /* @__PURE__ */ new Set();
|
|
9
9
|
var currentAppearance = "system";
|
|
10
|
+
var serverAppearance = "system";
|
|
11
|
+
/**
|
|
12
|
+
* Seed the SSR/hydration snapshot from the backend-shared `lattice.appearance`
|
|
13
|
+
* prop. The server render and the client's hydration pass must read the same
|
|
14
|
+
* value, or components branching on the appearance mismatch on hydration.
|
|
15
|
+
*/
|
|
16
|
+
function seedAppearance(value) {
|
|
17
|
+
if (isAppearance(value)) serverAppearance = value;
|
|
18
|
+
}
|
|
10
19
|
function isAppearance(value) {
|
|
11
20
|
return appearances.some((appearance) => appearance === value);
|
|
12
21
|
}
|
|
@@ -65,7 +74,7 @@ function initializeAppearance() {
|
|
|
65
74
|
mediaQuery()?.addEventListener("change", handleSystemAppearanceChange);
|
|
66
75
|
}
|
|
67
76
|
function useAppearance() {
|
|
68
|
-
const appearance = useSyncExternalStore(subscribe, () => currentAppearance, () =>
|
|
77
|
+
const appearance = useSyncExternalStore(subscribe, () => currentAppearance, () => serverAppearance);
|
|
69
78
|
const systemAppearance = useSyncExternalStore(subscribe, getSystemAppearance, () => "light");
|
|
70
79
|
return {
|
|
71
80
|
appearance,
|
|
@@ -74,6 +83,6 @@ function useAppearance() {
|
|
|
74
83
|
};
|
|
75
84
|
}
|
|
76
85
|
//#endregion
|
|
77
|
-
export { initializeAppearance, isAppearance, updateAppearance, useAppearance };
|
|
86
|
+
export { initializeAppearance, isAppearance, seedAppearance, updateAppearance, useAppearance };
|
|
78
87
|
|
|
79
88
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../resources/js/appearance/index.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\nconst appearances = [\"light\", \"dark\", \"system\"] as const;\n\nexport type Appearance = (typeof appearances)[number];\n\nexport type ResolvedAppearance = \"light\" | \"dark\";\n\nexport type UseAppearanceReturn = {\n readonly appearance: Appearance;\n readonly resolvedAppearance: ResolvedAppearance;\n readonly updateAppearance: (mode: Appearance) => void;\n};\n\nconst listeners = new Set<() => void>();\nlet currentAppearance: Appearance = \"system\";\n\nexport function isAppearance(value: unknown): value is Appearance {\n return appearances.some((appearance) => appearance === value);\n}\n\nconst prefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst setCookie = (name: string, value: string, days = 365): void => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const maxAge = days * 24 * 60 * 60;\n document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;\n};\n\nconst getStoredAppearance = (): Appearance => {\n if (typeof window === \"undefined\") {\n return \"system\";\n }\n\n const stored = localStorage.getItem(\"appearance\");\n\n return isAppearance(stored) ? stored : \"system\";\n};\n\nconst isDarkMode = (appearance: Appearance): boolean => {\n return appearance === \"dark\" || (appearance === \"system\" && prefersDark());\n};\n\nconst getSystemAppearance = (): ResolvedAppearance => (prefersDark() ? \"dark\" : \"light\");\n\nconst applyAppearance = (appearance: Appearance): void => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const isDark = isDarkMode(appearance);\n\n document.documentElement.classList.toggle(\"dark\", isDark);\n document.documentElement.style.colorScheme = isDark ? \"dark\" : \"light\";\n};\n\nconst subscribe = (callback: () => void) => {\n listeners.add(callback);\n\n return () => listeners.delete(callback);\n};\n\nconst notify = (): void => listeners.forEach((listener) => listener());\n\nconst mediaQuery = (): MediaQueryList | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\");\n};\n\nconst handleSystemAppearanceChange = (): void => {\n applyAppearance(currentAppearance);\n notify();\n};\n\nexport function updateAppearance(mode: Appearance): void {\n currentAppearance = mode;\n\n localStorage.setItem(\"appearance\", mode);\n\n setCookie(\"appearance\", mode);\n\n applyAppearance(mode);\n notify();\n}\n\nexport function initializeAppearance(): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n if (!localStorage.getItem(\"appearance\")) {\n localStorage.setItem(\"appearance\", \"system\");\n setCookie(\"appearance\", \"system\");\n }\n\n currentAppearance = getStoredAppearance();\n applyAppearance(currentAppearance);\n\n mediaQuery()?.addEventListener(\"change\", handleSystemAppearanceChange);\n}\n\nexport function useAppearance(): UseAppearanceReturn {\n const appearance: Appearance = useSyncExternalStore(\n subscribe,\n () => currentAppearance,\n () =>
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../resources/js/appearance/index.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\nconst appearances = [\"light\", \"dark\", \"system\"] as const;\n\nexport type Appearance = (typeof appearances)[number];\n\nexport type ResolvedAppearance = \"light\" | \"dark\";\n\nexport type UseAppearanceReturn = {\n readonly appearance: Appearance;\n readonly resolvedAppearance: ResolvedAppearance;\n readonly updateAppearance: (mode: Appearance) => void;\n};\n\nconst listeners = new Set<() => void>();\nlet currentAppearance: Appearance = \"system\";\nlet serverAppearance: Appearance = \"system\";\n\n/**\n * Seed the SSR/hydration snapshot from the backend-shared `lattice.appearance`\n * prop. The server render and the client's hydration pass must read the same\n * value, or components branching on the appearance mismatch on hydration.\n */\nexport function seedAppearance(value: unknown): void {\n if (isAppearance(value)) {\n serverAppearance = value;\n }\n}\n\nexport function isAppearance(value: unknown): value is Appearance {\n return appearances.some((appearance) => appearance === value);\n}\n\nconst prefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst setCookie = (name: string, value: string, days = 365): void => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const maxAge = days * 24 * 60 * 60;\n document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;\n};\n\nconst getStoredAppearance = (): Appearance => {\n if (typeof window === \"undefined\") {\n return \"system\";\n }\n\n const stored = localStorage.getItem(\"appearance\");\n\n return isAppearance(stored) ? stored : \"system\";\n};\n\nconst isDarkMode = (appearance: Appearance): boolean => {\n return appearance === \"dark\" || (appearance === \"system\" && prefersDark());\n};\n\nconst getSystemAppearance = (): ResolvedAppearance => (prefersDark() ? \"dark\" : \"light\");\n\nconst applyAppearance = (appearance: Appearance): void => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const isDark = isDarkMode(appearance);\n\n document.documentElement.classList.toggle(\"dark\", isDark);\n document.documentElement.style.colorScheme = isDark ? \"dark\" : \"light\";\n};\n\nconst subscribe = (callback: () => void) => {\n listeners.add(callback);\n\n return () => listeners.delete(callback);\n};\n\nconst notify = (): void => listeners.forEach((listener) => listener());\n\nconst mediaQuery = (): MediaQueryList | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\");\n};\n\nconst handleSystemAppearanceChange = (): void => {\n applyAppearance(currentAppearance);\n notify();\n};\n\nexport function updateAppearance(mode: Appearance): void {\n currentAppearance = mode;\n\n localStorage.setItem(\"appearance\", mode);\n\n setCookie(\"appearance\", mode);\n\n applyAppearance(mode);\n notify();\n}\n\nexport function initializeAppearance(): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n if (!localStorage.getItem(\"appearance\")) {\n localStorage.setItem(\"appearance\", \"system\");\n setCookie(\"appearance\", \"system\");\n }\n\n currentAppearance = getStoredAppearance();\n applyAppearance(currentAppearance);\n\n mediaQuery()?.addEventListener(\"change\", handleSystemAppearanceChange);\n}\n\nexport function useAppearance(): UseAppearanceReturn {\n const appearance: Appearance = useSyncExternalStore(\n subscribe,\n () => currentAppearance,\n () => serverAppearance,\n );\n const systemAppearance: ResolvedAppearance = useSyncExternalStore(\n subscribe,\n getSystemAppearance,\n () => \"light\",\n );\n\n const resolvedAppearance: ResolvedAppearance =\n appearance === \"system\" ? systemAppearance : appearance;\n\n return { appearance, resolvedAppearance, updateAppearance } as const;\n}\n"],"mappings":";;AAEA,IAAM,cAAc;CAAC;CAAS;CAAQ;AAAQ;AAY9C,IAAM,4BAAY,IAAI,IAAgB;AACtC,IAAI,oBAAgC;AACpC,IAAI,mBAA+B;;;;;;AAOnC,SAAgB,eAAe,OAAsB;CACnD,IAAI,aAAa,KAAK,GACpB,mBAAmB;AAEvB;AAEA,SAAgB,aAAa,OAAqC;CAChE,OAAO,YAAY,MAAM,eAAe,eAAe,KAAK;AAC9D;AAEA,IAAM,oBAA6B;CACjC,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,OAAO,OAAO,WAAW,8BAA8B,CAAC,CAAC;AAC3D;AAEA,IAAM,aAAa,MAAc,OAAe,OAAO,QAAc;CACnE,IAAI,OAAO,aAAa,aACtB;CAGF,MAAM,SAAS,OAAO,KAAK,KAAK;CAChC,SAAS,SAAS,GAAG,KAAK,GAAG,MAAM,kBAAkB,OAAO;AAC9D;AAEA,IAAM,4BAAwC;CAC5C,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,MAAM,SAAS,aAAa,QAAQ,YAAY;CAEhD,OAAO,aAAa,MAAM,IAAI,SAAS;AACzC;AAEA,IAAM,cAAc,eAAoC;CACtD,OAAO,eAAe,UAAW,eAAe,YAAY,YAAY;AAC1E;AAEA,IAAM,4BAAiD,YAAY,IAAI,SAAS;AAEhF,IAAM,mBAAmB,eAAiC;CACxD,IAAI,OAAO,aAAa,aACtB;CAGF,MAAM,SAAS,WAAW,UAAU;CAEpC,SAAS,gBAAgB,UAAU,OAAO,QAAQ,MAAM;CACxD,SAAS,gBAAgB,MAAM,cAAc,SAAS,SAAS;AACjE;AAEA,IAAM,aAAa,aAAyB;CAC1C,UAAU,IAAI,QAAQ;CAEtB,aAAa,UAAU,OAAO,QAAQ;AACxC;AAEA,IAAM,eAAqB,UAAU,SAAS,aAAa,SAAS,CAAC;AAErE,IAAM,mBAA0C;CAC9C,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,OAAO,OAAO,WAAW,8BAA8B;AACzD;AAEA,IAAM,qCAA2C;CAC/C,gBAAgB,iBAAiB;CACjC,OAAO;AACT;AAEA,SAAgB,iBAAiB,MAAwB;CACvD,oBAAoB;CAEpB,aAAa,QAAQ,cAAc,IAAI;CAEvC,UAAU,cAAc,IAAI;CAE5B,gBAAgB,IAAI;CACpB,OAAO;AACT;AAEA,SAAgB,uBAA6B;CAC3C,IAAI,OAAO,WAAW,aACpB;CAGF,IAAI,CAAC,aAAa,QAAQ,YAAY,GAAG;EACvC,aAAa,QAAQ,cAAc,QAAQ;EAC3C,UAAU,cAAc,QAAQ;CAClC;CAEA,oBAAoB,oBAAoB;CACxC,gBAAgB,iBAAiB;CAEjC,WAAW,CAAC,EAAE,iBAAiB,UAAU,4BAA4B;AACvE;AAEA,SAAgB,gBAAqC;CACnD,MAAM,aAAyB,qBAC7B,iBACM,yBACA,gBACR;CACA,MAAM,mBAAuC,qBAC3C,WACA,2BACM,OACR;CAKA,OAAO;EAAE;EAAY,oBAFnB,eAAe,WAAW,mBAAmB;EAEN;CAAiB;AAC5D"}
|
package/dist/create-app.d.ts
CHANGED
|
@@ -28,8 +28,10 @@ export type CreateLatticeAppOptions = Omit<InertiaAppOptions, "resolve" | "layou
|
|
|
28
28
|
/**
|
|
29
29
|
* Lattice's i18n bootstrap, on by default: when the backend shares the
|
|
30
30
|
* `lattice.i18n` prop, the first render waits for the translation setup (no
|
|
31
|
-
* flash of untranslated fallbacks
|
|
32
|
-
*
|
|
31
|
+
* flash of untranslated fallbacks; a server-rendered page instead hydrates
|
|
32
|
+
* immediately and swaps strings in once ready), `LocaleReload` re-fetches the
|
|
33
|
+
* page after a locale switch, and every visit carries the locale/timezone
|
|
34
|
+
* headers. The
|
|
33
35
|
* i18next chunk only loads when the backend actually shares the prop. Pass
|
|
34
36
|
* `false` to opt out entirely.
|
|
35
37
|
*/
|
|
@@ -37,7 +39,8 @@ export type CreateLatticeAppOptions = Omit<InertiaAppOptions, "resolve" | "layou
|
|
|
37
39
|
/**
|
|
38
40
|
* App bootstrap that needs the initial page — e.g. `configureEcho` from a
|
|
39
41
|
* shared connection prop. Runs on the client before the first render; a
|
|
40
|
-
* returned promise delays that render until it resolves
|
|
42
|
+
* returned promise delays that render until it resolves, except when
|
|
43
|
+
* hydrating a server-rendered page (hydration must match the SSR'd HTML).
|
|
41
44
|
*/
|
|
42
45
|
boot?: (context: {
|
|
43
46
|
page: InertiaPage;
|
package/dist/create-app.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { initializeAppearance } from "./appearance/index.js";
|
|
1
|
+
import { initializeAppearance, seedAppearance } from "./appearance/index.js";
|
|
2
2
|
import { setRefRefreshEndpoint } from "./core/api.js";
|
|
3
3
|
import { isRecord } from "./core/materialize.js";
|
|
4
4
|
import { extendRegistry } from "./core/registry.js";
|
|
5
5
|
import { setDefaultRegistry } from "./core/registry-context.js";
|
|
6
|
-
import "./i18n/instance.js";
|
|
6
|
+
import { holdI18nInit } from "./i18n/instance.js";
|
|
7
7
|
import { LocaleReload } from "./i18n/locale-reload.js";
|
|
8
8
|
import { i18nConfigFromPageProps } from "./i18n/shared-props.js";
|
|
9
9
|
import { createLayoutResolver, createPageResolver, registerRefRenewal, withVisitHeaders } from "./inertia.js";
|
|
@@ -63,8 +63,11 @@ function createLatticeApp({ registry: registry$1, plugins, sprite, pages = {}, d
|
|
|
63
63
|
layout: createLayoutResolver({ defaultLayout }),
|
|
64
64
|
withApp: (node, { ssr, page }) => {
|
|
65
65
|
const pending = [];
|
|
66
|
+
const shared = page.props.lattice;
|
|
67
|
+
let hydrating = false;
|
|
68
|
+
if (isRecord(shared)) seedAppearance(shared.appearance);
|
|
66
69
|
if (!ssr) {
|
|
67
|
-
|
|
70
|
+
hydrating = document.getElementById(inertiaOptions.id ?? "app")?.hasAttribute("data-server-rendered") === true;
|
|
68
71
|
if (isRecord(shared) && isRecord(shared.urls) && typeof shared.urls.refreshRef === "string") setRefRefreshEndpoint(shared.urls.refreshRef);
|
|
69
72
|
if (i18nEnabled && i18nConfigFromPageProps(page.props) !== void 0) pending.push(import("./i18n/page-props.js").then((module) => module.configureI18nFromPageProps(page.props, i18nOptions)));
|
|
70
73
|
const booted = boot?.({ page });
|
|
@@ -76,10 +79,15 @@ function createLatticeApp({ registry: registry$1, plugins, sprite, pages = {}, d
|
|
|
76
79
|
sprite,
|
|
77
80
|
children: wrap ? wrap(inner) : inner
|
|
78
81
|
});
|
|
79
|
-
|
|
82
|
+
if (pending.length === 0) return shell;
|
|
83
|
+
if (hydrating) {
|
|
84
|
+
holdI18nInit(Promise.all(pending));
|
|
85
|
+
return shell;
|
|
86
|
+
}
|
|
87
|
+
return /* @__PURE__ */ jsx(AwaitReady, {
|
|
80
88
|
ready: Promise.all(pending),
|
|
81
89
|
children: shell
|
|
82
|
-
})
|
|
90
|
+
});
|
|
83
91
|
}
|
|
84
92
|
});
|
|
85
93
|
initializeAppearance();
|
package/dist/create-app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-app.js","names":[],"sources":["../resources/js/create-app.tsx"],"sourcesContent":["import type { Page as InertiaPage, VisitOptions } from \"@inertiajs/core\";\nimport { createInertiaApp } from \"@inertiajs/react\";\nimport { useEffect, useState, type ReactElement, type ReactNode } from \"react\";\nimport { initializeAppearance } from \"./appearance\";\nimport { setRefRefreshEndpoint } from \"./core/api\";\nimport { isRecord } from \"./core/materialize\";\nimport { extendRegistry, type Plugin, type PluginI18n, type Registry } from \"./core/registry\";\nimport { setDefaultRegistry } from \"./core/registry-context\";\nimport type { SpriteValue } from \"./icons/sprite\";\nimport { DEFAULT_NAMESPACE } from \"./i18n/instance\";\nimport { LocaleReload } from \"./i18n/locale-reload\";\nimport { i18nConfigFromPageProps } from \"./i18n/shared-props\";\nimport {\n createLayoutResolver,\n createPageResolver,\n registerRefRenewal,\n withVisitHeaders,\n type CreateLayoutResolverOptions,\n type PageModules,\n} from \"./inertia\";\nimport { ProviderBase } from \"./provider-base\";\nimport { registry as defaultRegistry } from \"./registry\";\n\ntype InertiaAppOptions = NonNullable<Parameters<typeof createInertiaApp>[0]>;\n\nexport type CreateLatticeAppI18nOptions = {\n /**\n * i18next namespaces to load, e.g. `[\"lattice\", \"app\"]`. Defaults to the\n * package's own. Namespaces declared by plugins are merged in on top.\n */\n namespaces?: string[];\n};\n\nexport type CreateLatticeAppOptions = Omit<\n InertiaAppOptions,\n \"resolve\" | \"layout\" | \"setup\" | \"withApp\" | \"pages\"\n> & {\n registry?: Registry;\n /**\n * Component-package plugins to merge onto the registry — pass the\n * Composer-discovered `virtual:lattice/plugins` here to register vendor\n * components with no manual `extendRegistry` call. A plugin's declared\n * i18n namespace joins the i18n bootstrap automatically.\n */\n plugins?: Plugin[];\n sprite?: SpriteValue;\n /** Normal Inertia page modules, e.g. `import.meta.glob('./pages/**\\/*.tsx')`. */\n pages?: PageModules;\n defaultLayout?: CreateLayoutResolverOptions[\"defaultLayout\"];\n /**\n * Lattice's i18n bootstrap, on by default: when the backend shares the\n * `lattice.i18n` prop, the first render waits for the translation setup (no\n * flash of untranslated fallbacks), `LocaleReload` re-fetches the page after\n * a locale switch, and every visit carries the locale/timezone headers. The\n * i18next chunk only loads when the backend actually shares the prop. Pass\n * `false` to opt out entirely.\n */\n i18n?: CreateLatticeAppI18nOptions | false;\n /**\n * App bootstrap that needs the initial page — e.g. `configureEcho` from a\n * shared connection prop. Runs on the client before the first render; a\n * returned promise delays that render until it resolves.\n */\n boot?: (context: { page: InertiaPage }) => void | Promise<void>;\n /**\n * Compose providers or siblings around the app. Applied inside the Lattice\n * Provider, so the registry, sprite, and toaster contexts are available.\n */\n wrap?: (app: ReactElement) => ReactElement;\n};\n\nfunction withPluginNamespaces(\n options: CreateLatticeAppI18nOptions,\n entries: PluginI18n[],\n): CreateLatticeAppI18nOptions {\n if (entries.length === 0) {\n return options;\n }\n\n const namespaces = new Set([\n ...(options.namespaces ?? [DEFAULT_NAMESPACE]),\n ...entries.map((entry) => entry.namespace),\n ]);\n\n return { ...options, namespaces: [...namespaces] };\n}\n\n// Fail-open: a failed bootstrap renders the app anyway (with fallback strings)\n// rather than a blank page.\nfunction AwaitReady({ ready, children }: { ready: Promise<unknown>; children: ReactNode }) {\n const [isReady, setReady] = useState(false);\n\n useEffect(() => {\n let active = true;\n const reveal = (): void => {\n if (active) {\n setReady(true);\n }\n };\n\n void ready.then(reveal, reveal);\n\n return () => {\n active = false;\n };\n }, [ready]);\n\n return isReady ? children : null;\n}\n\n/**\n * Bootstrap an Inertia app with the Lattice shell: the page/layout resolvers\n * (server-driven Lattice pages and normal Inertia pages alike), the Provider —\n * registry, sprite, flash toasts via Lattice's own Toaster — theme\n * initialization, and the i18n bootstrap. Forwards any other createInertiaApp\n * option.\n */\nexport function createLatticeApp({\n registry,\n plugins,\n sprite,\n pages = {},\n defaultLayout,\n strictMode = true,\n i18n,\n boot,\n wrap,\n ...inertiaOptions\n}: CreateLatticeAppOptions = {}) {\n const baseRegistry = registry ?? defaultRegistry;\n const activeRegistry =\n plugins && plugins.length > 0 ? extendRegistry(baseRegistry, ...plugins) : baseRegistry;\n\n setDefaultRegistry(activeRegistry);\n registerRefRenewal();\n\n const i18nEnabled = i18n !== false;\n const pluginI18n = plugins?.flatMap((plugin) => (plugin.i18n ? [plugin.i18n] : [])) ?? [];\n const i18nOptions = i18nEnabled ? withPluginNamespaces(i18n ?? {}, pluginI18n) : {};\n const userVisitOptions = inertiaOptions.defaults?.visitOptions;\n const defaults = i18nEnabled\n ? {\n ...inertiaOptions.defaults,\n visitOptions: (href: string, options: VisitOptions): VisitOptions =>\n withVisitHeaders(href, userVisitOptions?.(href, options) ?? options),\n }\n : inertiaOptions.defaults;\n\n const app = createInertiaApp({\n ...inertiaOptions,\n defaults,\n strictMode,\n resolve: createPageResolver(pages),\n layout: createLayoutResolver({ defaultLayout }),\n withApp: (node: ReactElement, { ssr, page }: { ssr: boolean; page: InertiaPage }) => {\n const pending: Promise<unknown>[] = [];\n\n if (!ssr) {\n const shared = page.props.lattice;\n\n if (\n isRecord(shared) &&\n isRecord(shared.urls) &&\n typeof shared.urls.refreshRef === \"string\"\n ) {\n setRefRefreshEndpoint(shared.urls.refreshRef);\n }\n\n // Loaded on demand so apps without the shared i18n prop never ship the\n // i18next backend; the disabled-config call still stores the timezone.\n if (i18nEnabled && i18nConfigFromPageProps(page.props) !== undefined) {\n pending.push(\n import(\"./i18n/page-props\").then((module) =>\n module.configureI18nFromPageProps(page.props, i18nOptions),\n ),\n );\n }\n\n const booted = boot?.({ page });\n\n if (booted) {\n pending.push(booted);\n }\n }\n\n const inner = (\n <>\n {node}\n {i18nEnabled ? <LocaleReload /> : null}\n </>\n );\n\n // The whole shell waits, not just the page: anything rendering a\n // translated string (the Toaster included) would otherwise initialize\n // i18next before the backend can register — init runs exactly once,\n // first caller wins.\n const shell = (\n <ProviderBase registry={activeRegistry} sprite={sprite}>\n {wrap ? wrap(inner) : inner}\n </ProviderBase>\n );\n\n return pending.length > 0 ? (\n <AwaitReady ready={Promise.all(pending)}>{shell}</AwaitReady>\n ) : (\n shell\n );\n },\n });\n\n initializeAppearance();\n\n return app;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuEA,SAAS,qBACP,SACA,SAC6B;CAC7B,IAAI,QAAQ,WAAW,GACrB,OAAO;CAGT,MAAM,6BAAa,IAAI,IAAI,CACzB,GAAI,QAAQ,cAAc,CAAA,SAAkB,GAC5C,GAAG,QAAQ,KAAK,UAAU,MAAM,SAAS,CAC3C,CAAC;CAED,OAAO;EAAE,GAAG;EAAS,YAAY,CAAC,GAAG,UAAU;CAAE;AACnD;AAIA,SAAS,WAAW,EAAE,OAAO,YAA8D;CACzF,MAAM,CAAC,SAAS,YAAY,SAAS,KAAK;CAE1C,gBAAgB;EACd,IAAI,SAAS;EACb,MAAM,eAAqB;GACzB,IAAI,QACF,SAAS,IAAI;EAEjB;EAEA,MAAW,KAAK,QAAQ,MAAM;EAE9B,aAAa;GACX,SAAS;EACX;CACF,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO,UAAU,WAAW;AAC9B;;;;;;;;AASA,SAAgB,iBAAiB,EAC/B,UAAA,YACA,SACA,QACA,QAAQ,CAAC,GACT,eACA,aAAa,MACb,MACA,MACA,MACA,GAAG,mBACwB,CAAC,GAAG;CAC/B,MAAM,eAAe,cAAY;CACjC,MAAM,iBACJ,WAAW,QAAQ,SAAS,IAAI,eAAe,cAAc,GAAG,OAAO,IAAI;CAE7E,mBAAmB,cAAc;CACjC,mBAAmB;CAEnB,MAAM,cAAc,SAAS;CAC7B,MAAM,aAAa,SAAS,SAAS,WAAY,OAAO,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,CAAE,KAAK,CAAC;CACxF,MAAM,cAAc,cAAc,qBAAqB,QAAQ,CAAC,GAAG,UAAU,IAAI,CAAC;CAClF,MAAM,mBAAmB,eAAe,UAAU;CAClD,MAAM,WAAW,cACb;EACE,GAAG,eAAe;EAClB,eAAe,MAAc,YAC3B,iBAAiB,MAAM,mBAAmB,MAAM,OAAO,KAAK,OAAO;CACvE,IACA,eAAe;CAEnB,MAAM,MAAM,iBAAiB;EAC3B,GAAG;EACH;EACA;EACA,SAAS,mBAAmB,KAAK;EACjC,QAAQ,qBAAqB,EAAE,cAAc,CAAC;EAC9C,UAAU,MAAoB,EAAE,KAAK,WAAgD;GACnF,MAAM,UAA8B,CAAC;GAErC,IAAI,CAAC,KAAK;IACR,MAAM,SAAS,KAAK,MAAM;IAE1B,IACE,SAAS,MAAM,KACf,SAAS,OAAO,IAAI,KACpB,OAAO,OAAO,KAAK,eAAe,UAElC,sBAAsB,OAAO,KAAK,UAAU;IAK9C,IAAI,eAAe,wBAAwB,KAAK,KAAK,MAAM,KAAA,GACzD,QAAQ,KACN,OAAO,uBAAoB,CAAC,MAAM,WAChC,OAAO,2BAA2B,KAAK,OAAO,WAAW,CAC3D,CACF;IAGF,MAAM,SAAS,OAAO,EAAE,KAAK,CAAC;IAE9B,IAAI,QACF,QAAQ,KAAK,MAAM;GAEvB;GAEA,MAAM,QACJ,qBAAA,YAAA,EAAA,UAAA,CACG,MACA,cAAc,oBAAC,cAAD,CAAe,CAAA,IAAI,IAClC,EAAA,CAAA;GAOJ,MAAM,QACJ,oBAAC,cAAD;IAAc,UAAU;IAAwB;IAC7C,UAAA,OAAO,KAAK,KAAK,IAAI;GACV,CAAA;GAGhB,OAAO,QAAQ,SAAS,IACtB,oBAAC,YAAD;IAAY,OAAO,QAAQ,IAAI,OAAO;IAAI,UAAA;GAAkB,CAAA,IAE5D;EAEJ;CACF,CAAC;CAED,qBAAqB;CAErB,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"create-app.js","names":[],"sources":["../resources/js/create-app.tsx"],"sourcesContent":["import type { Page as InertiaPage, VisitOptions } from \"@inertiajs/core\";\nimport { createInertiaApp } from \"@inertiajs/react\";\nimport { useEffect, useState, type ReactElement, type ReactNode } from \"react\";\nimport { initializeAppearance, seedAppearance } from \"./appearance\";\nimport { setRefRefreshEndpoint } from \"./core/api\";\nimport { isRecord } from \"./core/materialize\";\nimport { extendRegistry, type Plugin, type PluginI18n, type Registry } from \"./core/registry\";\nimport { setDefaultRegistry } from \"./core/registry-context\";\nimport type { SpriteValue } from \"./icons/sprite\";\nimport { DEFAULT_NAMESPACE, holdI18nInit } from \"./i18n/instance\";\nimport { LocaleReload } from \"./i18n/locale-reload\";\nimport { i18nConfigFromPageProps } from \"./i18n/shared-props\";\nimport {\n createLayoutResolver,\n createPageResolver,\n registerRefRenewal,\n withVisitHeaders,\n type CreateLayoutResolverOptions,\n type PageModules,\n} from \"./inertia\";\nimport { ProviderBase } from \"./provider-base\";\nimport { registry as defaultRegistry } from \"./registry\";\n\ntype InertiaAppOptions = NonNullable<Parameters<typeof createInertiaApp>[0]>;\n\nexport type CreateLatticeAppI18nOptions = {\n /**\n * i18next namespaces to load, e.g. `[\"lattice\", \"app\"]`. Defaults to the\n * package's own. Namespaces declared by plugins are merged in on top.\n */\n namespaces?: string[];\n};\n\nexport type CreateLatticeAppOptions = Omit<\n InertiaAppOptions,\n \"resolve\" | \"layout\" | \"setup\" | \"withApp\" | \"pages\"\n> & {\n registry?: Registry;\n /**\n * Component-package plugins to merge onto the registry — pass the\n * Composer-discovered `virtual:lattice/plugins` here to register vendor\n * components with no manual `extendRegistry` call. A plugin's declared\n * i18n namespace joins the i18n bootstrap automatically.\n */\n plugins?: Plugin[];\n sprite?: SpriteValue;\n /** Normal Inertia page modules, e.g. `import.meta.glob('./pages/**\\/*.tsx')`. */\n pages?: PageModules;\n defaultLayout?: CreateLayoutResolverOptions[\"defaultLayout\"];\n /**\n * Lattice's i18n bootstrap, on by default: when the backend shares the\n * `lattice.i18n` prop, the first render waits for the translation setup (no\n * flash of untranslated fallbacks; a server-rendered page instead hydrates\n * immediately and swaps strings in once ready), `LocaleReload` re-fetches the\n * page after a locale switch, and every visit carries the locale/timezone\n * headers. The\n * i18next chunk only loads when the backend actually shares the prop. Pass\n * `false` to opt out entirely.\n */\n i18n?: CreateLatticeAppI18nOptions | false;\n /**\n * App bootstrap that needs the initial page — e.g. `configureEcho` from a\n * shared connection prop. Runs on the client before the first render; a\n * returned promise delays that render until it resolves, except when\n * hydrating a server-rendered page (hydration must match the SSR'd HTML).\n */\n boot?: (context: { page: InertiaPage }) => void | Promise<void>;\n /**\n * Compose providers or siblings around the app. Applied inside the Lattice\n * Provider, so the registry, sprite, and toaster contexts are available.\n */\n wrap?: (app: ReactElement) => ReactElement;\n};\n\nfunction withPluginNamespaces(\n options: CreateLatticeAppI18nOptions,\n entries: PluginI18n[],\n): CreateLatticeAppI18nOptions {\n if (entries.length === 0) {\n return options;\n }\n\n const namespaces = new Set([\n ...(options.namespaces ?? [DEFAULT_NAMESPACE]),\n ...entries.map((entry) => entry.namespace),\n ]);\n\n return { ...options, namespaces: [...namespaces] };\n}\n\n// Fail-open: a failed bootstrap renders the app anyway (with fallback strings)\n// rather than a blank page.\nfunction AwaitReady({ ready, children }: { ready: Promise<unknown>; children: ReactNode }) {\n const [isReady, setReady] = useState(false);\n\n useEffect(() => {\n let active = true;\n const reveal = (): void => {\n if (active) {\n setReady(true);\n }\n };\n\n void ready.then(reveal, reveal);\n\n return () => {\n active = false;\n };\n }, [ready]);\n\n return isReady ? children : null;\n}\n\n/**\n * Bootstrap an Inertia app with the Lattice shell: the page/layout resolvers\n * (server-driven Lattice pages and normal Inertia pages alike), the Provider —\n * registry, sprite, flash toasts via Lattice's own Toaster — theme\n * initialization, and the i18n bootstrap. Forwards any other createInertiaApp\n * option.\n */\nexport function createLatticeApp({\n registry,\n plugins,\n sprite,\n pages = {},\n defaultLayout,\n strictMode = true,\n i18n,\n boot,\n wrap,\n ...inertiaOptions\n}: CreateLatticeAppOptions = {}) {\n const baseRegistry = registry ?? defaultRegistry;\n const activeRegistry =\n plugins && plugins.length > 0 ? extendRegistry(baseRegistry, ...plugins) : baseRegistry;\n\n setDefaultRegistry(activeRegistry);\n registerRefRenewal();\n\n const i18nEnabled = i18n !== false;\n const pluginI18n = plugins?.flatMap((plugin) => (plugin.i18n ? [plugin.i18n] : [])) ?? [];\n const i18nOptions = i18nEnabled ? withPluginNamespaces(i18n ?? {}, pluginI18n) : {};\n const userVisitOptions = inertiaOptions.defaults?.visitOptions;\n const defaults = i18nEnabled\n ? {\n ...inertiaOptions.defaults,\n visitOptions: (href: string, options: VisitOptions): VisitOptions =>\n withVisitHeaders(href, userVisitOptions?.(href, options) ?? options),\n }\n : inertiaOptions.defaults;\n\n const app = createInertiaApp({\n ...inertiaOptions,\n defaults,\n strictMode,\n resolve: createPageResolver(pages),\n layout: createLayoutResolver({ defaultLayout }),\n withApp: (node: ReactElement, { ssr, page }: { ssr: boolean; page: InertiaPage }) => {\n const pending: Promise<unknown>[] = [];\n const shared = page.props.lattice;\n let hydrating = false;\n\n if (isRecord(shared)) {\n seedAppearance(shared.appearance);\n }\n\n if (!ssr) {\n hydrating =\n document\n .getElementById(inertiaOptions.id ?? \"app\")\n ?.hasAttribute(\"data-server-rendered\") === true;\n\n if (\n isRecord(shared) &&\n isRecord(shared.urls) &&\n typeof shared.urls.refreshRef === \"string\"\n ) {\n setRefRefreshEndpoint(shared.urls.refreshRef);\n }\n\n // Loaded on demand so apps without the shared i18n prop never ship the\n // i18next backend; the disabled-config call still stores the timezone.\n if (i18nEnabled && i18nConfigFromPageProps(page.props) !== undefined) {\n pending.push(\n import(\"./i18n/page-props\").then((module) =>\n module.configureI18nFromPageProps(page.props, i18nOptions),\n ),\n );\n }\n\n const booted = boot?.({ page });\n\n if (booted) {\n pending.push(booted);\n }\n }\n\n const inner = (\n <>\n {node}\n {i18nEnabled ? <LocaleReload /> : null}\n </>\n );\n\n // The whole shell waits, not just the page: anything rendering a\n // translated string (the Toaster included) would otherwise initialize\n // i18next before the backend can register — init runs exactly once,\n // first caller wins.\n const shell = (\n <ProviderBase registry={activeRegistry} sprite={sprite}>\n {wrap ? wrap(inner) : inner}\n </ProviderBase>\n );\n\n if (pending.length === 0) {\n return shell;\n }\n\n // A hydration pass must render what the server rendered — gating on the\n // bootstrap would hydrate null against the SSR'd markup. The hold still\n // lets the i18n backend win the init race; strings swap in once it resolves.\n if (hydrating) {\n holdI18nInit(Promise.all(pending));\n\n return shell;\n }\n\n return <AwaitReady ready={Promise.all(pending)}>{shell}</AwaitReady>;\n },\n });\n\n initializeAppearance();\n\n return app;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useLayoutEffect as useLayoutEffect$1 } from "../../../lib/use-layout-effect.js";
|
|
2
|
+
import { useCallback, useRef } from "react";
|
|
2
3
|
//#region resources/js/form/components/fields/use-flip-reorder.ts
|
|
3
4
|
var DURATION_MS = 180;
|
|
4
5
|
function prefersReducedMotion() {
|
|
@@ -16,7 +17,7 @@ function useFlipReorder(orderSignature) {
|
|
|
16
17
|
if (el) elements.current.set(key, el);
|
|
17
18
|
else elements.current.delete(key);
|
|
18
19
|
}, []);
|
|
19
|
-
useLayoutEffect(() => {
|
|
20
|
+
useLayoutEffect$1(() => {
|
|
20
21
|
const handles = [];
|
|
21
22
|
const next = /* @__PURE__ */ new Map();
|
|
22
23
|
elements.current.forEach((el, key) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-flip-reorder.js","names":[],"sources":["../../../../resources/js/form/components/fields/use-flip-reorder.ts"],"sourcesContent":["import { useCallback,
|
|
1
|
+
{"version":3,"file":"use-flip-reorder.js","names":[],"sources":["../../../../resources/js/form/components/fields/use-flip-reorder.ts"],"sourcesContent":["import { useCallback, useRef } from \"react\";\nimport { useLayoutEffect } from \"@lattice-php/lattice/lib/use-layout-effect\";\n\nconst DURATION_MS = 180;\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\n/**\n * FLIP reorder: animates registered elements from their previous to current\n * position whenever `orderSignature` changes. Imperative style writes only, so\n * it does not affect React render or the memoised rows.\n */\nexport function useFlipReorder(\n orderSignature: string,\n): (key: string, el: HTMLElement | null) => void {\n const elements = useRef(new Map<string, HTMLElement>());\n const previous = useRef(new Map<string, DOMRect>());\n\n // register is the FLIP measurement target; the element must be a zero-overhead positioning wrapper (no padding/margin) for the delta to match the visible row.\n const register = useCallback((key: string, el: HTMLElement | null): void => {\n if (el) {\n elements.current.set(key, el);\n } else {\n elements.current.delete(key);\n }\n }, []);\n\n useLayoutEffect(() => {\n const handles: number[] = [];\n const next = new Map<string, DOMRect>();\n elements.current.forEach((el, key) => {\n el.style.transition = \"\";\n el.style.transform = \"\";\n next.set(key, el.getBoundingClientRect());\n });\n\n if (!prefersReducedMotion()) {\n elements.current.forEach((el, key) => {\n const before = previous.current.get(key);\n const after = next.get(key);\n if (before && after) {\n const dy = before.top - after.top;\n if (dy) {\n el.style.transform = `translateY(${dy}px)`;\n handles.push(\n requestAnimationFrame(() => {\n el.style.transition = `transform ${DURATION_MS}ms ease-out`;\n el.style.transform = \"\";\n }),\n );\n }\n }\n });\n }\n\n previous.current = next;\n\n return () => {\n for (const handle of handles) {\n cancelAnimationFrame(handle);\n }\n };\n }, [orderSignature]);\n\n return register;\n}\n"],"mappings":";;;AAGA,IAAM,cAAc;AAEpB,SAAS,uBAAgC;CACvC,OAAO,OAAO,eAAe,cAAc,WAAW,kCAAkC,CAAC,CAAC;AAC5F;;;;;;AAOA,SAAgB,eACd,gBAC+C;CAC/C,MAAM,WAAW,uBAAO,IAAI,IAAyB,CAAC;CACtD,MAAM,WAAW,uBAAO,IAAI,IAAqB,CAAC;CAGlD,MAAM,WAAW,aAAa,KAAa,OAAiC;EAC1E,IAAI,IACF,SAAS,QAAQ,IAAI,KAAK,EAAE;OAE5B,SAAS,QAAQ,OAAO,GAAG;CAE/B,GAAG,CAAC,CAAC;CAEL,wBAAsB;EACpB,MAAM,UAAoB,CAAC;EAC3B,MAAM,uBAAO,IAAI,IAAqB;EACtC,SAAS,QAAQ,SAAS,IAAI,QAAQ;GACpC,GAAG,MAAM,aAAa;GACtB,GAAG,MAAM,YAAY;GACrB,KAAK,IAAI,KAAK,GAAG,sBAAsB,CAAC;EAC1C,CAAC;EAED,IAAI,CAAC,qBAAqB,GACxB,SAAS,QAAQ,SAAS,IAAI,QAAQ;GACpC,MAAM,SAAS,SAAS,QAAQ,IAAI,GAAG;GACvC,MAAM,QAAQ,KAAK,IAAI,GAAG;GAC1B,IAAI,UAAU,OAAO;IACnB,MAAM,KAAK,OAAO,MAAM,MAAM;IAC9B,IAAI,IAAI;KACN,GAAG,MAAM,YAAY,cAAc,GAAG;KACtC,QAAQ,KACN,4BAA4B;MAC1B,GAAG,MAAM,aAAa,aAAa,YAAY;MAC/C,GAAG,MAAM,YAAY;KACvB,CAAC,CACH;IACF;GACF;EACF,CAAC;EAGH,SAAS,UAAU;EAEnB,aAAa;GACX,KAAK,MAAM,UAAU,SACnB,qBAAqB,MAAM;EAE/B;CACF,GAAG,CAAC,cAAc,CAAC;CAEnB,OAAO;AACT"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { duplicateRow, ensureRowIds, moveRow, removeRow, seedRows, withRowId } from "./repeater-rows.js";
|
|
2
2
|
import { useFieldScope } from "../../hooks/field-scope.js";
|
|
3
3
|
import { useFormValue, useSetFormValue } from "../../hooks/values.js";
|
|
4
|
-
import {
|
|
4
|
+
import { useLayoutEffect as useLayoutEffect$1 } from "../../../lib/use-layout-effect.js";
|
|
5
|
+
import { useCallback } from "react";
|
|
5
6
|
//#region resources/js/form/components/fields/use-row-collection.ts
|
|
6
7
|
function useRowCollection(name, defaultItems) {
|
|
7
8
|
const scope = useFieldScope();
|
|
@@ -10,7 +11,7 @@ function useRowCollection(name, defaultItems) {
|
|
|
10
11
|
const stored = useFormValue(path);
|
|
11
12
|
const raw = Array.isArray(stored) ? stored : seedRows(stored, defaultItems);
|
|
12
13
|
const rows = ensureRowIds(raw);
|
|
13
|
-
useLayoutEffect(() => {
|
|
14
|
+
useLayoutEffect$1(() => {
|
|
14
15
|
if (rows !== raw) setValue(path, rows);
|
|
15
16
|
}, [
|
|
16
17
|
raw,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-row-collection.js","names":[],"sources":["../../../../resources/js/form/components/fields/use-row-collection.ts"],"sourcesContent":["import { useCallback
|
|
1
|
+
{"version":3,"file":"use-row-collection.js","names":[],"sources":["../../../../resources/js/form/components/fields/use-row-collection.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useLayoutEffect } from \"@lattice-php/lattice/lib/use-layout-effect\";\nimport { useFieldScope } from \"@lattice-php/lattice/form/hooks/field-scope\";\nimport { useFormValue, useSetFormValue } from \"@lattice-php/lattice/form/hooks/values\";\nimport {\n duplicateRow,\n ensureRowIds,\n moveRow,\n removeRow,\n seedRows,\n withRowId,\n type RepeaterRow,\n} from \"./repeater-rows\";\n\ntype RowCollection = {\n path: string;\n rows: RepeaterRow[];\n onField: (index: number, field: string, value: unknown) => void;\n onRemove: (index: number) => void;\n onMove: (index: number, delta: number) => void;\n onDuplicate: (index: number) => void;\n append: (row: RepeaterRow) => void;\n};\n\nexport function useRowCollection(name: string, defaultItems: number): RowCollection {\n const scope = useFieldScope();\n const path = scope ? scope.errorKey(name) : name;\n const setValue = useSetFormValue();\n const stored = useFormValue(path);\n const raw: RepeaterRow[] = Array.isArray(stored) ? stored : seedRows(stored, defaultItems);\n const rows = ensureRowIds(raw);\n\n useLayoutEffect(() => {\n if (rows !== raw) {\n setValue(path, rows);\n }\n }, [raw, rows, setValue, path]);\n\n // Functional store updates preserve the identity of untouched rows, which lets\n // the memoised RowItem skip re-rendering siblings on a single-row edit.\n const mutate = useCallback(\n (fn: (rows: RepeaterRow[]) => RepeaterRow[]): void => {\n setValue(path, (prev: unknown) =>\n fn(Array.isArray(prev) ? (prev as RepeaterRow[]) : seedRows(prev, defaultItems)),\n );\n },\n [setValue, path, defaultItems],\n );\n\n const onField = useCallback(\n (index: number, field: string, value: unknown): void =>\n mutate((current) => current.map((r, i) => (i === index ? { ...r, [field]: value } : r))),\n [mutate],\n );\n const onRemove = useCallback(\n (index: number): void => mutate((current) => removeRow(current, index)),\n [mutate],\n );\n const onMove = useCallback(\n (index: number, delta: number): void =>\n mutate((current) => moveRow(current, index, index + delta)),\n [mutate],\n );\n const onDuplicate = useCallback(\n (index: number): void => mutate((current) => duplicateRow(current, index)),\n [mutate],\n );\n const append = useCallback(\n (row: RepeaterRow): void => mutate((current) => [...current, withRowId(row)]),\n [mutate],\n );\n\n return { path, rows, onField, onRemove, onMove, onDuplicate, append };\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,iBAAiB,MAAc,cAAqC;CAClF,MAAM,QAAQ,cAAc;CAC5B,MAAM,OAAO,QAAQ,MAAM,SAAS,IAAI,IAAI;CAC5C,MAAM,WAAW,gBAAgB;CACjC,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,MAAqB,MAAM,QAAQ,MAAM,IAAI,SAAS,SAAS,QAAQ,YAAY;CACzF,MAAM,OAAO,aAAa,GAAG;CAE7B,wBAAsB;EACpB,IAAI,SAAS,KACX,SAAS,MAAM,IAAI;CAEvB,GAAG;EAAC;EAAK;EAAM;EAAU;CAAI,CAAC;CAI9B,MAAM,SAAS,aACZ,OAAqD;EACpD,SAAS,OAAO,SACd,GAAG,MAAM,QAAQ,IAAI,IAAK,OAAyB,SAAS,MAAM,YAAY,CAAC,CACjF;CACF,GACA;EAAC;EAAU;EAAM;CAAY,CAC/B;CAyBA,OAAO;EAAE;EAAM;EAAM,SAvBL,aACb,OAAe,OAAe,UAC7B,QAAQ,YAAY,QAAQ,KAAK,GAAG,MAAO,MAAM,QAAQ;GAAE,GAAG;IAAI,QAAQ;EAAM,IAAI,CAAE,CAAC,GACzF,CAAC,MAAM,CAoBY;EAAS,UAlBb,aACd,UAAwB,QAAQ,YAAY,UAAU,SAAS,KAAK,CAAC,GACtE,CAAC,MAAM,CAgBqB;EAAU,QAdzB,aACZ,OAAe,UACd,QAAQ,YAAY,QAAQ,SAAS,OAAO,QAAQ,KAAK,CAAC,GAC5D,CAAC,MAAM,CAW+B;EAAQ,aAT5B,aACjB,UAAwB,QAAQ,YAAY,aAAa,SAAS,KAAK,CAAC,GACzE,CAAC,MAAM,CAOuC;EAAa,QAL9C,aACZ,QAA2B,QAAQ,YAAY,CAAC,GAAG,SAAS,UAAU,GAAG,CAAC,CAAC,GAC5E,CAAC,MAAM,CAGoD;CAAO;AACtE"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { walkFields } from "../lib/field-props.js";
|
|
2
2
|
import { getPath } from "../lib/form-path.js";
|
|
3
3
|
import { useFormValues, useSetFormValue } from "./values.js";
|
|
4
|
+
import { useLayoutEffect as useLayoutEffect$1 } from "../../lib/use-layout-effect.js";
|
|
4
5
|
import { postFormAction } from "../lib/form-transport.js";
|
|
5
6
|
import { collectPrefillTargets, pathsToClear, pruneOverrides, seededOverrides } from "../lib/prefill-targets.js";
|
|
6
|
-
import { useCallback, useEffect,
|
|
7
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
8
|
//#region resources/js/form/hooks/use-form-resolver.ts
|
|
8
9
|
function useFormResolver(action, componentRef, nodes) {
|
|
9
10
|
const values = useFormValues();
|
|
@@ -16,7 +17,7 @@ function useFormResolver(action, componentRef, nodes) {
|
|
|
16
17
|
const previousTargets = useRef(targets);
|
|
17
18
|
const targetsRef = useRef(targets);
|
|
18
19
|
targetsRef.current = targets;
|
|
19
|
-
useLayoutEffect(() => {
|
|
20
|
+
useLayoutEffect$1(() => {
|
|
20
21
|
const liveOverrideKeys = new Set(targets.map((target) => target.overrideKey));
|
|
21
22
|
seededOverrideKeys.current = new Set([...seededOverrideKeys.current].filter((overrideKey) => liveOverrideKeys.has(overrideKey)));
|
|
22
23
|
const freshTargets = targets.filter((target) => !seededOverrideKeys.current.has(target.overrideKey));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-form-resolver.js","names":[],"sources":["../../../resources/js/form/hooks/use-form-resolver.ts"],"sourcesContent":["import { useCallback, useEffect,
|
|
1
|
+
{"version":3,"file":"use-form-resolver.js","names":[],"sources":["../../../resources/js/form/hooks/use-form-resolver.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useLayoutEffect } from \"@lattice-php/lattice/lib/use-layout-effect\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport type { ResolveResponse } from \"@lattice-php/lattice/types/generated\";\nimport { walkFields } from \"@lattice-php/lattice/form/lib/field-props\";\nimport { FORM_DEBOUNCE_MS, postFormAction } from \"@lattice-php/lattice/form/lib/form-transport\";\nimport {\n collectPrefillTargets,\n getPath,\n pathsToClear,\n pruneOverrides,\n seededOverrides,\n} from \"@lattice-php/lattice/form/lib/prefill-targets\";\nimport type { PrefillController } from \"./prefill-context\";\nimport { useFormValues, useSetFormValue } from \"./values\";\n\ntype FormResolver = {\n nodes: Record<string, Node>;\n markUserEdit: PrefillController[\"markUserEdit\"];\n};\n\nexport function useFormResolver(\n action: string,\n componentRef: string,\n nodes: Node[] | undefined,\n): FormResolver {\n const values = useFormValues();\n const setValue = useSetFormValue();\n const [resolved, setResolved] = useState<Record<string, Node>>({});\n\n const targets = useMemo(() => collectPrefillTargets(nodes, values), [nodes, values]);\n\n const overrides = useRef<Set<string>>(new Set());\n const seededOverrideKeys = useRef<Set<string>>(new Set());\n const previousValues = useRef<Record<string, unknown>>(values);\n const previousTargets = useRef(targets);\n\n // Read targets via a ref inside the effect: `targets` changes identity on every\n // value change, but the effect must fire only when `watchSignature` changes\n // (which already reflects added/removed target paths). Keeping `targets` out of\n // the dep array preserves the targeted watch in non-\"any\" mode.\n const targetsRef = useRef(targets);\n targetsRef.current = targets;\n\n useLayoutEffect(() => {\n const liveOverrideKeys = new Set(targets.map((target) => target.overrideKey));\n seededOverrideKeys.current = new Set(\n [...seededOverrideKeys.current].filter((overrideKey) => liveOverrideKeys.has(overrideKey)),\n );\n const freshTargets = targets.filter(\n (target) => !seededOverrideKeys.current.has(target.overrideKey),\n );\n\n // Layout ordering seeds loaded values before the first resolve effect can apply prefill.\n for (const overrideKey of seededOverrides(freshTargets, values)) {\n overrides.current.add(overrideKey);\n }\n\n for (const target of freshTargets) {\n seededOverrideKeys.current.add(target.overrideKey);\n }\n }, [targets, values]);\n\n const markUserEdit = useCallback((overrideKey: string) => {\n overrides.current.add(overrideKey);\n }, []);\n\n const watch = useMemo(() => {\n const keys = new Set<string>();\n let any = false;\n walkFields(nodes, (props) => {\n if (Array.isArray(props.dependsOnKeys)) {\n for (const key of props.dependsOnKeys) {\n keys.add(String(key));\n }\n }\n if (props.dependsOnAny) {\n any = true;\n }\n });\n return { keys: [...keys], any };\n }, [nodes]);\n\n const watchPaths = useMemo(() => {\n const set = new Set<string>(watch.keys);\n for (const target of targets) {\n for (const dep of target.resetOn) {\n set.add(dep);\n }\n for (const dep of target.refreshOn) {\n set.add(dep);\n }\n }\n return [...set];\n }, [watch.keys, targets]);\n\n // In \"any\" mode the values store keeps a stable reference until something\n // actually changes, so its identity is the change signal. Otherwise hash the\n // watched paths (form-level and per-row).\n const watchSignature = watch.any\n ? values\n : JSON.stringify(watchPaths.map((path) => getPath(values, path)));\n\n useEffect(() => {\n if (watchPaths.length === 0 && !watch.any) {\n return;\n }\n\n const previous = previousValues.current;\n previousValues.current = values;\n\n // `resetOn` deps unlock a path (a fresh product gives a fresh price); `refreshOn`\n // deps only re-ask the server and never clear an override (a customer change\n // re-prices untouched rows but leaves user-edited ones alone).\n for (const overrideKey of pathsToClear(\n { targets: previousTargets.current, values: previous },\n { targets: targetsRef.current, values },\n )) {\n overrides.current.delete(overrideKey);\n }\n previousTargets.current = targetsRef.current;\n\n // Override ownership is row-id keyed, so pruning removes departed rows without reindex drift.\n overrides.current = pruneOverrides(overrides.current, targetsRef.current);\n\n const controller = new AbortController();\n\n const timer = window.setTimeout(() => {\n void postFormAction<ResolveResponse>(\n action,\n componentRef,\n { _sub: \"resolve\", ...values },\n controller.signal,\n )\n .then((response) => {\n if (!response) {\n return;\n }\n for (const [name, value] of Object.entries(response.values ?? {})) {\n setValue(name, value);\n }\n const targetsByPath = new Map(\n targetsRef.current.map((target) => [target.path, target] as const),\n );\n for (const [path, value] of Object.entries(response.prefill ?? {})) {\n const target = targetsByPath.get(path);\n\n if (target && !overrides.current.has(target.overrideKey)) {\n setValue(path, value);\n }\n }\n if (response.fields) {\n setResolved(response.fields);\n }\n })\n .catch(() => {});\n }, FORM_DEBOUNCE_MS);\n\n return () => {\n window.clearTimeout(timer);\n controller.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [watchSignature, action, componentRef, watch.any, setValue]);\n\n return { nodes: resolved, markUserEdit };\n}\n"],"mappings":"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"}
|
package/dist/i18n/instance.d.ts
CHANGED
|
@@ -10,6 +10,14 @@ type TranslationResult = {
|
|
|
10
10
|
setLocale: (locale: string) => void;
|
|
11
11
|
};
|
|
12
12
|
export declare const i18n: I18nInstance;
|
|
13
|
+
/**
|
|
14
|
+
* Defer component-driven initialization until `until` settles — for shells
|
|
15
|
+
* that render before the i18n bootstrap has loaded (SSR hydration), where the
|
|
16
|
+
* first rendered `useT` would otherwise win the init race and lock the HTTP
|
|
17
|
+
* backend out. Configuring callers (those passing `extend` to {@link ensureI18n})
|
|
18
|
+
* bypass the hold; a failed bootstrap releases it, so init stays fail-open.
|
|
19
|
+
*/
|
|
20
|
+
export declare function holdI18nInit(until: Promise<unknown>): void;
|
|
13
21
|
/**
|
|
14
22
|
* Initialize the instance exactly once. The first caller wins — a rendered
|
|
15
23
|
* component (inline English, zero config) or `enableBackend()`, which registers
|
package/dist/i18n/instance.js
CHANGED
|
@@ -7,6 +7,7 @@ import i18next from "i18next";
|
|
|
7
7
|
var DEFAULT_NAMESPACE = "lattice";
|
|
8
8
|
var i18n = i18next.createInstance();
|
|
9
9
|
var initialization;
|
|
10
|
+
var hold;
|
|
10
11
|
var revision = 0;
|
|
11
12
|
function subscribe(onStoreChange) {
|
|
12
13
|
const listener = () => {
|
|
@@ -30,11 +31,26 @@ function snapshot() {
|
|
|
30
31
|
return revision;
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
34
|
+
* Defer component-driven initialization until `until` settles — for shells
|
|
35
|
+
* that render before the i18n bootstrap has loaded (SSR hydration), where the
|
|
36
|
+
* first rendered `useT` would otherwise win the init race and lock the HTTP
|
|
37
|
+
* backend out. Configuring callers (those passing `extend` to {@link ensureI18n})
|
|
38
|
+
* bypass the hold; a failed bootstrap releases it, so init stays fail-open.
|
|
39
|
+
*/
|
|
40
|
+
function holdI18nInit(until) {
|
|
41
|
+
const release = () => {
|
|
42
|
+
hold = void 0;
|
|
43
|
+
};
|
|
44
|
+
const released = until.then(release, release);
|
|
45
|
+
if (!initialization) hold = released;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
33
48
|
* Initialize the instance exactly once. The first caller wins — a rendered
|
|
34
49
|
* component (inline English, zero config) or `enableBackend()`, which registers
|
|
35
50
|
* its backend first because i18next can only wire a backend during `init`.
|
|
36
51
|
*/
|
|
37
52
|
function ensureI18n(extend) {
|
|
53
|
+
if (!initialization && hold && !extend) return hold.then(() => ensureI18n());
|
|
38
54
|
if (!initialization) {
|
|
39
55
|
const base = {
|
|
40
56
|
lng: currentLocale(),
|
|
@@ -92,6 +108,6 @@ function translate(namespace, key, defaultValue, options) {
|
|
|
92
108
|
});
|
|
93
109
|
}
|
|
94
110
|
//#endregion
|
|
95
|
-
export { DEFAULT_NAMESPACE, ensureI18n, i18n, preloadLanguages, translate, useT };
|
|
111
|
+
export { DEFAULT_NAMESPACE, ensureI18n, holdI18nInit, i18n, preloadLanguages, translate, useT };
|
|
96
112
|
|
|
97
113
|
//# sourceMappingURL=instance.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.js","names":[],"sources":["../../resources/js/i18n/instance.ts"],"sourcesContent":["import i18next, { type i18n as I18nInstance, type InitOptions } from \"i18next\";\nimport { useCallback, useSyncExternalStore } from \"react\";\nimport { useConfig } from \"./config\";\nimport { registerDateTimeFormatter } from \"./date-time-formatter\";\nimport { currentLocale, subscribeLocale, useLocale } from \"./locale\";\n\nexport const DEFAULT_NAMESPACE = \"lattice\";\n\ntype TranslationFunction = (\n key: string,\n defaultValue?: string,\n options?: Record<string, unknown>,\n) => string;\n\ntype TranslationResult = {\n t: TranslationFunction;\n i18n: I18nInstance;\n locale: string;\n locales: readonly string[];\n ready: boolean;\n setLocale: (locale: string) => void;\n};\n\nexport const i18n: I18nInstance = i18next.createInstance();\n\nlet initialization: Promise<unknown> | undefined;\nlet revision = 0;\n\nfunction subscribe(onStoreChange: () => void): () => void {\n const listener = () => {\n revision += 1;\n onStoreChange();\n };\n\n i18n.on(\"initialized\", listener);\n i18n.on(\"loaded\", listener);\n i18n.on(\"languageChanged\", listener);\n i18n.store?.on(\"added\", listener);\n i18n.store?.on(\"removed\", listener);\n\n return () => {\n i18n.off(\"initialized\", listener);\n i18n.off(\"loaded\", listener);\n i18n.off(\"languageChanged\", listener);\n i18n.store?.off(\"added\", listener);\n i18n.store?.off(\"removed\", listener);\n };\n}\n\nfunction snapshot(): number {\n return revision;\n}\n\n/**\n * Initialize the instance exactly once. The first caller wins — a rendered\n * component (inline English, zero config) or `enableBackend()`, which registers\n * its backend first because i18next can only wire a backend during `init`.\n */\nexport function ensureI18n(extend?: (base: InitOptions) => InitOptions): Promise<unknown> {\n if (!initialization) {\n const base: InitOptions = {\n lng: currentLocale(),\n fallbackLng: \"en\",\n ns: [DEFAULT_NAMESPACE],\n defaultNS: DEFAULT_NAMESPACE,\n interpolation: { escapeValue: false },\n };\n\n initialization = i18n.init(extend ? extend(base) : base);\n // `services.formatter` only exists once `init()` has run, so this must follow it.\n registerDateTimeFormatter(i18n);\n }\n\n return initialization;\n}\n\nsubscribeLocale((locale) => {\n void ensureI18n().then(() => {\n if (i18n.language !== locale) {\n void i18n.changeLanguage(locale);\n }\n });\n});\n\n/**\n * Eagerly load the namespaces for the given locales so a later\n * `changeLanguage` resolves them from the store instead of an HTTP round-trip,\n * which would otherwise flash the fallback language on switch. The active\n * locale is already loaded by {@link ensureI18n}, so it is skipped.\n */\nexport async function preloadLanguages(locales: readonly string[]): Promise<void> {\n const pending = locales.filter((locale) => locale !== currentLocale());\n\n if (pending.length === 0) {\n return;\n }\n\n await ensureI18n();\n await i18n.loadLanguages([...pending]);\n}\n\nexport function useT(namespace: string): TranslationResult {\n ensureI18n();\n useSyncExternalStore(subscribe, snapshot, snapshot);\n const { locales } = useConfig();\n const { locale, setLocale } = useLocale();\n\n const t = useCallback<TranslationFunction>(\n (key, defaultValue = key, options = {}) => translate(namespace, key, defaultValue, options),\n [namespace],\n );\n\n return { t, i18n, locale, locales, ready: i18n.isInitialized, setLocale };\n}\n\nexport function translate(\n namespace: string,\n key: string,\n defaultValue: string,\n options?: Record<string, unknown>,\n): string {\n ensureI18n();\n\n if (!i18n.isInitialized || !i18n.hasLoadedNamespace(namespace)) {\n return i18n.t(key, defaultValue, { ns: namespace, ...options, saveMissing: false });\n }\n\n return i18n.t(key, defaultValue, { ns: namespace, ...options });\n}\n"],"mappings":";;;;;;AAMA,IAAa,oBAAoB;AAiBjC,IAAa,OAAqB,QAAQ,eAAe;AAEzD,IAAI;AACJ,IAAI,WAAW;AAEf,SAAS,UAAU,eAAuC;CACxD,MAAM,iBAAiB;EACrB,YAAY;EACZ,cAAc;CAChB;CAEA,KAAK,GAAG,eAAe,QAAQ;CAC/B,KAAK,GAAG,UAAU,QAAQ;CAC1B,KAAK,GAAG,mBAAmB,QAAQ;CACnC,KAAK,OAAO,GAAG,SAAS,QAAQ;CAChC,KAAK,OAAO,GAAG,WAAW,QAAQ;CAElC,aAAa;EACX,KAAK,IAAI,eAAe,QAAQ;EAChC,KAAK,IAAI,UAAU,QAAQ;EAC3B,KAAK,IAAI,mBAAmB,QAAQ;EACpC,KAAK,OAAO,IAAI,SAAS,QAAQ;EACjC,KAAK,OAAO,IAAI,WAAW,QAAQ;CACrC;AACF;AAEA,SAAS,WAAmB;CAC1B,OAAO;AACT;;;;;;AAOA,SAAgB,WAAW,QAA+D;CACxF,IAAI,CAAC,gBAAgB;EACnB,MAAM,OAAoB;GACxB,KAAK,cAAc;GACnB,aAAa;GACb,IAAI,CAAC,iBAAiB;GACtB,WAAW;GACX,eAAe,EAAE,aAAa,MAAM;EACtC;EAEA,iBAAiB,KAAK,KAAK,SAAS,OAAO,IAAI,IAAI,IAAI;EAEvD,0BAA0B,IAAI;CAChC;CAEA,OAAO;AACT;AAEA,iBAAiB,WAAW;CAC1B,WAAgB,CAAC,CAAC,WAAW;EAC3B,IAAI,KAAK,aAAa,QACpB,KAAU,eAAe,MAAM;CAEnC,CAAC;AACH,CAAC;;;;;;;AAQD,eAAsB,iBAAiB,SAA2C;CAChF,MAAM,UAAU,QAAQ,QAAQ,WAAW,WAAW,cAAc,CAAC;CAErE,IAAI,QAAQ,WAAW,GACrB;CAGF,MAAM,WAAW;CACjB,MAAM,KAAK,cAAc,CAAC,GAAG,OAAO,CAAC;AACvC;AAEA,SAAgB,KAAK,WAAsC;CACzD,WAAW;CACX,qBAAqB,WAAW,UAAU,QAAQ;CAClD,MAAM,EAAE,YAAY,UAAU;CAC9B,MAAM,EAAE,QAAQ,cAAc,UAAU;CAOxC,OAAO;EAAE,GALC,aACP,KAAK,eAAe,KAAK,UAAU,CAAC,MAAM,UAAU,WAAW,KAAK,cAAc,OAAO,GAC1F,CAAC,SAAS,CAGH;EAAG;EAAM;EAAQ;EAAS,OAAO,KAAK;EAAe;CAAU;AAC1E;AAEA,SAAgB,UACd,WACA,KACA,cACA,SACQ;CACR,WAAW;CAEX,IAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,mBAAmB,SAAS,GAC3D,OAAO,KAAK,EAAE,KAAK,cAAc;EAAE,IAAI;EAAW,GAAG;EAAS,aAAa;CAAM,CAAC;CAGpF,OAAO,KAAK,EAAE,KAAK,cAAc;EAAE,IAAI;EAAW,GAAG;CAAQ,CAAC;AAChE"}
|
|
1
|
+
{"version":3,"file":"instance.js","names":[],"sources":["../../resources/js/i18n/instance.ts"],"sourcesContent":["import i18next, { type i18n as I18nInstance, type InitOptions } from \"i18next\";\nimport { useCallback, useSyncExternalStore } from \"react\";\nimport { useConfig } from \"./config\";\nimport { registerDateTimeFormatter } from \"./date-time-formatter\";\nimport { currentLocale, subscribeLocale, useLocale } from \"./locale\";\n\nexport const DEFAULT_NAMESPACE = \"lattice\";\n\ntype TranslationFunction = (\n key: string,\n defaultValue?: string,\n options?: Record<string, unknown>,\n) => string;\n\ntype TranslationResult = {\n t: TranslationFunction;\n i18n: I18nInstance;\n locale: string;\n locales: readonly string[];\n ready: boolean;\n setLocale: (locale: string) => void;\n};\n\nexport const i18n: I18nInstance = i18next.createInstance();\n\nlet initialization: Promise<unknown> | undefined;\nlet hold: Promise<unknown> | undefined;\nlet revision = 0;\n\nfunction subscribe(onStoreChange: () => void): () => void {\n const listener = () => {\n revision += 1;\n onStoreChange();\n };\n\n i18n.on(\"initialized\", listener);\n i18n.on(\"loaded\", listener);\n i18n.on(\"languageChanged\", listener);\n i18n.store?.on(\"added\", listener);\n i18n.store?.on(\"removed\", listener);\n\n return () => {\n i18n.off(\"initialized\", listener);\n i18n.off(\"loaded\", listener);\n i18n.off(\"languageChanged\", listener);\n i18n.store?.off(\"added\", listener);\n i18n.store?.off(\"removed\", listener);\n };\n}\n\nfunction snapshot(): number {\n return revision;\n}\n\n/**\n * Defer component-driven initialization until `until` settles — for shells\n * that render before the i18n bootstrap has loaded (SSR hydration), where the\n * first rendered `useT` would otherwise win the init race and lock the HTTP\n * backend out. Configuring callers (those passing `extend` to {@link ensureI18n})\n * bypass the hold; a failed bootstrap releases it, so init stays fail-open.\n */\nexport function holdI18nInit(until: Promise<unknown>): void {\n const release = (): void => {\n hold = undefined;\n };\n const released = until.then(release, release);\n\n if (!initialization) {\n hold = released;\n }\n}\n\n/**\n * Initialize the instance exactly once. The first caller wins — a rendered\n * component (inline English, zero config) or `enableBackend()`, which registers\n * its backend first because i18next can only wire a backend during `init`.\n */\nexport function ensureI18n(extend?: (base: InitOptions) => InitOptions): Promise<unknown> {\n if (!initialization && hold && !extend) {\n return hold.then(() => ensureI18n());\n }\n\n if (!initialization) {\n const base: InitOptions = {\n lng: currentLocale(),\n fallbackLng: \"en\",\n ns: [DEFAULT_NAMESPACE],\n defaultNS: DEFAULT_NAMESPACE,\n interpolation: { escapeValue: false },\n };\n\n initialization = i18n.init(extend ? extend(base) : base);\n // `services.formatter` only exists once `init()` has run, so this must follow it.\n registerDateTimeFormatter(i18n);\n }\n\n return initialization;\n}\n\nsubscribeLocale((locale) => {\n void ensureI18n().then(() => {\n if (i18n.language !== locale) {\n void i18n.changeLanguage(locale);\n }\n });\n});\n\n/**\n * Eagerly load the namespaces for the given locales so a later\n * `changeLanguage` resolves them from the store instead of an HTTP round-trip,\n * which would otherwise flash the fallback language on switch. The active\n * locale is already loaded by {@link ensureI18n}, so it is skipped.\n */\nexport async function preloadLanguages(locales: readonly string[]): Promise<void> {\n const pending = locales.filter((locale) => locale !== currentLocale());\n\n if (pending.length === 0) {\n return;\n }\n\n await ensureI18n();\n await i18n.loadLanguages([...pending]);\n}\n\nexport function useT(namespace: string): TranslationResult {\n ensureI18n();\n useSyncExternalStore(subscribe, snapshot, snapshot);\n const { locales } = useConfig();\n const { locale, setLocale } = useLocale();\n\n const t = useCallback<TranslationFunction>(\n (key, defaultValue = key, options = {}) => translate(namespace, key, defaultValue, options),\n [namespace],\n );\n\n return { t, i18n, locale, locales, ready: i18n.isInitialized, setLocale };\n}\n\nexport function translate(\n namespace: string,\n key: string,\n defaultValue: string,\n options?: Record<string, unknown>,\n): string {\n ensureI18n();\n\n if (!i18n.isInitialized || !i18n.hasLoadedNamespace(namespace)) {\n return i18n.t(key, defaultValue, { ns: namespace, ...options, saveMissing: false });\n }\n\n return i18n.t(key, defaultValue, { ns: namespace, ...options });\n}\n"],"mappings":";;;;;;AAMA,IAAa,oBAAoB;AAiBjC,IAAa,OAAqB,QAAQ,eAAe;AAEzD,IAAI;AACJ,IAAI;AACJ,IAAI,WAAW;AAEf,SAAS,UAAU,eAAuC;CACxD,MAAM,iBAAiB;EACrB,YAAY;EACZ,cAAc;CAChB;CAEA,KAAK,GAAG,eAAe,QAAQ;CAC/B,KAAK,GAAG,UAAU,QAAQ;CAC1B,KAAK,GAAG,mBAAmB,QAAQ;CACnC,KAAK,OAAO,GAAG,SAAS,QAAQ;CAChC,KAAK,OAAO,GAAG,WAAW,QAAQ;CAElC,aAAa;EACX,KAAK,IAAI,eAAe,QAAQ;EAChC,KAAK,IAAI,UAAU,QAAQ;EAC3B,KAAK,IAAI,mBAAmB,QAAQ;EACpC,KAAK,OAAO,IAAI,SAAS,QAAQ;EACjC,KAAK,OAAO,IAAI,WAAW,QAAQ;CACrC;AACF;AAEA,SAAS,WAAmB;CAC1B,OAAO;AACT;;;;;;;;AASA,SAAgB,aAAa,OAA+B;CAC1D,MAAM,gBAAsB;EAC1B,OAAO,KAAA;CACT;CACA,MAAM,WAAW,MAAM,KAAK,SAAS,OAAO;CAE5C,IAAI,CAAC,gBACH,OAAO;AAEX;;;;;;AAOA,SAAgB,WAAW,QAA+D;CACxF,IAAI,CAAC,kBAAkB,QAAQ,CAAC,QAC9B,OAAO,KAAK,WAAW,WAAW,CAAC;CAGrC,IAAI,CAAC,gBAAgB;EACnB,MAAM,OAAoB;GACxB,KAAK,cAAc;GACnB,aAAa;GACb,IAAI,CAAC,iBAAiB;GACtB,WAAW;GACX,eAAe,EAAE,aAAa,MAAM;EACtC;EAEA,iBAAiB,KAAK,KAAK,SAAS,OAAO,IAAI,IAAI,IAAI;EAEvD,0BAA0B,IAAI;CAChC;CAEA,OAAO;AACT;AAEA,iBAAiB,WAAW;CAC1B,WAAgB,CAAC,CAAC,WAAW;EAC3B,IAAI,KAAK,aAAa,QACpB,KAAU,eAAe,MAAM;CAEnC,CAAC;AACH,CAAC;;;;;;;AAQD,eAAsB,iBAAiB,SAA2C;CAChF,MAAM,UAAU,QAAQ,QAAQ,WAAW,WAAW,cAAc,CAAC;CAErE,IAAI,QAAQ,WAAW,GACrB;CAGF,MAAM,WAAW;CACjB,MAAM,KAAK,cAAc,CAAC,GAAG,OAAO,CAAC;AACvC;AAEA,SAAgB,KAAK,WAAsC;CACzD,WAAW;CACX,qBAAqB,WAAW,UAAU,QAAQ;CAClD,MAAM,EAAE,YAAY,UAAU;CAC9B,MAAM,EAAE,QAAQ,cAAc,UAAU;CAOxC,OAAO;EAAE,GALC,aACP,KAAK,eAAe,KAAK,UAAU,CAAC,MAAM,UAAU,WAAW,KAAK,cAAc,OAAO,GAC1F,CAAC,SAAS,CAGH;EAAG;EAAM;EAAQ;EAAS,OAAO,KAAK;EAAe;CAAU;AAC1E;AAEA,SAAgB,UACd,WACA,KACA,cACA,SACQ;CACR,WAAW;CAEX,IAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,mBAAmB,SAAS,GAC3D,OAAO,KAAK,EAAE,KAAK,cAAc;EAAE,IAAI;EAAW,GAAG;EAAS,aAAa;CAAM,CAAC;CAGpF,OAAO,KAAK,EAAE,KAAK,cAAc;EAAE,IAAI;EAAW,GAAG;CAAQ,CAAC;AAChE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect as useLayoutEffect$1 } from "react";
|
|
2
|
+
//#region resources/js/lib/use-layout-effect.ts
|
|
3
|
+
/**
|
|
4
|
+
* Drop-in for React's `useLayoutEffect`, which warns under `renderToString`;
|
|
5
|
+
* effects never run there, so the server-side substitution is free.
|
|
6
|
+
*/
|
|
7
|
+
var useLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect$1;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { useLayoutEffect };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=use-layout-effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-layout-effect.js","names":[],"sources":["../../resources/js/lib/use-layout-effect.ts"],"sourcesContent":["// oxlint-disable-next-line no-restricted-imports -- the one place the real hook may be imported\nimport { useEffect, useLayoutEffect as reactUseLayoutEffect } from \"react\";\n\n/**\n * Drop-in for React's `useLayoutEffect`, which warns under `renderToString`;\n * effects never run there, so the server-side substitution is free.\n */\nexport const useLayoutEffect = typeof window === \"undefined\" ? useEffect : reactUseLayoutEffect;\n"],"mappings":";;;;;;AAOA,IAAa,kBAAkB,OAAO,WAAW,cAAc,YAAY"}
|
package/dist/ssr.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { InertiaAppSSRResponse, Page } from '@inertiajs/core';
|
|
2
|
+
import { CreateLatticeAppOptions } from './create-app.js';
|
|
3
|
+
/**
|
|
4
|
+
* Build an SSR entry's render function from the same options as `createLatticeApp`.
|
|
5
|
+
* `@inertiajs/vite` only auto-wraps literal `createInertiaApp` calls, so a Lattice
|
|
6
|
+
* app declares its own `resources/js/ssr.tsx`:
|
|
7
|
+
*
|
|
8
|
+
* import createServer from "@inertiajs/react/server";
|
|
9
|
+
* import { createLatticeSsr } from "@lattice-php/lattice/ssr";
|
|
10
|
+
*
|
|
11
|
+
* createServer(createLatticeSsr({ ...same options as app.tsx }));
|
|
12
|
+
*
|
|
13
|
+
* The plugin rewrites that `createServer` call into the dev-server default
|
|
14
|
+
* export plus the production HTTP bootstrap; where the bootstrap is unwanted,
|
|
15
|
+
* `export default createLatticeSsr(...)` works as-is.
|
|
16
|
+
*
|
|
17
|
+
* Own subpath on purpose: importing react-dom/server from the client bundle
|
|
18
|
+
* would drag the server renderer into the app.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createLatticeSsr(options?: CreateLatticeAppOptions): (page: Page) => Promise<InertiaAppSSRResponse>;
|
package/dist/ssr.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createLatticeApp } from "./create-app.js";
|
|
2
|
+
import { renderToString } from "react-dom/server";
|
|
3
|
+
//#region resources/js/ssr.ts
|
|
4
|
+
/**
|
|
5
|
+
* Build an SSR entry's render function from the same options as `createLatticeApp`.
|
|
6
|
+
* `@inertiajs/vite` only auto-wraps literal `createInertiaApp` calls, so a Lattice
|
|
7
|
+
* app declares its own `resources/js/ssr.tsx`:
|
|
8
|
+
*
|
|
9
|
+
* import createServer from "@inertiajs/react/server";
|
|
10
|
+
* import { createLatticeSsr } from "@lattice-php/lattice/ssr";
|
|
11
|
+
*
|
|
12
|
+
* createServer(createLatticeSsr({ ...same options as app.tsx }));
|
|
13
|
+
*
|
|
14
|
+
* The plugin rewrites that `createServer` call into the dev-server default
|
|
15
|
+
* export plus the production HTTP bootstrap; where the bootstrap is unwanted,
|
|
16
|
+
* `export default createLatticeSsr(...)` works as-is.
|
|
17
|
+
*
|
|
18
|
+
* Own subpath on purpose: importing react-dom/server from the client bundle
|
|
19
|
+
* would drag the server renderer into the app.
|
|
20
|
+
*/
|
|
21
|
+
function createLatticeSsr(options = {}) {
|
|
22
|
+
const app = createLatticeApp(options);
|
|
23
|
+
return async (page) => {
|
|
24
|
+
const render = await app;
|
|
25
|
+
if (typeof render !== "function") throw new Error("[lattice] createLatticeSsr requires a server environment without a DOM.");
|
|
26
|
+
return render(page, renderToString);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { createLatticeSsr };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=ssr.js.map
|
package/dist/ssr.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr.js","names":[],"sources":["../resources/js/ssr.ts"],"sourcesContent":["import type { InertiaAppSSRResponse, Page } from \"@inertiajs/core\";\nimport { renderToString } from \"react-dom/server\";\nimport { createLatticeApp, type CreateLatticeAppOptions } from \"./create-app\";\n\n/**\n * Build an SSR entry's render function from the same options as `createLatticeApp`.\n * `@inertiajs/vite` only auto-wraps literal `createInertiaApp` calls, so a Lattice\n * app declares its own `resources/js/ssr.tsx`:\n *\n * import createServer from \"@inertiajs/react/server\";\n * import { createLatticeSsr } from \"@lattice-php/lattice/ssr\";\n *\n * createServer(createLatticeSsr({ ...same options as app.tsx }));\n *\n * The plugin rewrites that `createServer` call into the dev-server default\n * export plus the production HTTP bootstrap; where the bootstrap is unwanted,\n * `export default createLatticeSsr(...)` works as-is.\n *\n * Own subpath on purpose: importing react-dom/server from the client bundle\n * would drag the server renderer into the app.\n */\nexport function createLatticeSsr(\n options: CreateLatticeAppOptions = {},\n): (page: Page) => Promise<InertiaAppSSRResponse> {\n const app = createLatticeApp(options);\n\n return async (page: Page): Promise<InertiaAppSSRResponse> => {\n const render = await app;\n\n if (typeof render !== \"function\") {\n throw new Error(\"[lattice] createLatticeSsr requires a server environment without a DOM.\");\n }\n\n return render(page, renderToString);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,iBACd,UAAmC,CAAC,GACY;CAChD,MAAM,MAAM,iBAAiB,OAAO;CAEpC,OAAO,OAAO,SAA+C;EAC3D,MAAM,SAAS,MAAM;EAErB,IAAI,OAAO,WAAW,YACpB,MAAM,IAAI,MAAM,yEAAyE;EAG3F,OAAO,OAAO,MAAM,cAAc;CACpC;AACF"}
|
package/dist/vite.js
CHANGED
|
@@ -99,13 +99,10 @@ function latticeConfig(options = {}) {
|
|
|
99
99
|
const { appRoot, root } = resolveRoots(options);
|
|
100
100
|
return {
|
|
101
101
|
resolve: {
|
|
102
|
-
|
|
102
|
+
...options.source ? { alias: {
|
|
103
103
|
"@lattice-php/lattice/css": path.resolve(root, "resources/css/lattice.css"),
|
|
104
104
|
"@lattice-php/lattice": path.resolve(root, "resources/js")
|
|
105
|
-
} : {
|
|
106
|
-
react: path.resolve(appRoot, "node_modules/react"),
|
|
107
|
-
"react-dom": path.resolve(appRoot, "node_modules/react-dom")
|
|
108
|
-
},
|
|
105
|
+
} } : {},
|
|
109
106
|
dedupe: [
|
|
110
107
|
"@inertiajs/react",
|
|
111
108
|
"react",
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","names":[],"sources":["../resources/js/vite.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { svgSprite } from \"@lattice-php/vite-svg-sprite\";\nimport type { IconTypesOptions, SvgSpriteOptions } from \"@lattice-php/vite-svg-sprite\";\nimport { searchForWorkspaceRoot } from \"vite\";\nimport type { Plugin, PluginOption, UserConfig } from \"vite\";\nimport { refreshTypeScriptTypes } from \"./vite-typescript-refresh\";\n\ntype InlineDependency = string | RegExp;\n\ntype ConfigWithTest = UserConfig & {\n test?: {\n server?: {\n deps?: {\n inline?: InlineDependency[];\n };\n };\n };\n};\n\nexport type LatticeViteIconsOptions = Omit<SvgSpriteOptions, \"dts\" | \"iconDirs\"> & {\n dirs?: string[];\n dts?: Partial<IconTypesOptions> | false;\n};\n\nexport type LatticeViteOptions = {\n appRoot?: string;\n icons?: boolean | LatticeViteIconsOptions;\n root?: string;\n source?: boolean;\n /** Refresh generated TypeScript types via the dev server. Defaults to `true`. */\n typescript?: boolean;\n};\n\ntype Roots = {\n appRoot: string;\n root: string;\n};\n\nexport function lattice(options: LatticeViteOptions = {}): PluginOption[] {\n const { appRoot } = resolveRoots(options);\n const plugins: PluginOption[] = [\n corePlugin(options),\n optionalPeersPlugin(),\n componentPackagesPlugin(discoverComponentPackages(appRoot)),\n typescriptPlugin(options),\n ];\n const iconOptions = resolveIconOptions(options);\n\n if (iconOptions) {\n plugins.push(svgSprite(iconOptions));\n }\n\n return plugins;\n}\n\n/** A Composer package that contributes a Lattice component plugin. */\nexport type LatticeComponentPackage = {\n name: string;\n /** Absolute path to the package's installed directory. */\n dir: string;\n /** Absolute path to the package's JS plugin entry (its `createPlugin(...)`). */\n plugin: string;\n};\n\ntype InstalledPackage = {\n name: string;\n \"install-path\"?: string;\n extra?: { lattice?: { plugin?: string } };\n};\n\ntype RootPackageJson = {\n name?: string;\n extra?: { lattice?: { plugin?: string } };\n};\n\n/**\n * Resolve every Composer package that declares `extra.lattice.plugin` into an\n * absolute plugin-entry path. `installPathsRelativeTo` is `vendor/composer` (the\n * dir `installed.json` records its `install-path`s against).\n */\nexport function collectComponentPackages(\n installed: { packages?: InstalledPackage[] } | InstalledPackage[],\n installPathsRelativeTo: string,\n): LatticeComponentPackage[] {\n const packages = Array.isArray(installed) ? installed : (installed.packages ?? []);\n\n return packages.flatMap((pkg) => {\n const entry = pkg.extra?.lattice?.plugin;\n\n if (typeof entry !== \"string\") {\n return [];\n }\n\n const dir = path.resolve(installPathsRelativeTo, pkg[\"install-path\"] ?? `../${pkg.name}`);\n\n return [{ name: pkg.name, dir, plugin: path.resolve(dir, entry) }];\n });\n}\n\n/**\n * Resolve the composer ROOT project's own `extra.lattice.plugin` — Composer\n * never lists the root package in `installed.json`, so a component package\n * declaring the plugin entry in its own composer.json would otherwise be\n * invisible to its own dev server (e.g. inside a testbench workbench, where\n * the package itself is the app root).\n */\nexport function collectRootComponentPackage(\n composerJson: RootPackageJson,\n appRoot: string,\n): LatticeComponentPackage[] {\n const entry = composerJson.extra?.lattice?.plugin;\n\n if (typeof entry !== \"string\" || typeof composerJson.name !== \"string\") {\n return [];\n }\n\n return [{ name: composerJson.name, dir: appRoot, plugin: path.resolve(appRoot, entry) }];\n}\n\n/**\n * Read `<appRoot>/vendor/composer/installed.json` and `<appRoot>/composer.json`\n * and collect every component package they contribute.\n */\nexport function discoverComponentPackages(appRoot: string): LatticeComponentPackage[] {\n const composerDir = path.resolve(appRoot, \"vendor/composer\");\n\n let installed: LatticeComponentPackage[] = [];\n\n try {\n const raw = readFileSync(path.join(composerDir, \"installed.json\"), \"utf8\");\n installed = collectComponentPackages(JSON.parse(raw), composerDir);\n } catch {\n installed = [];\n }\n\n let root: LatticeComponentPackage[] = [];\n\n try {\n const raw = readFileSync(path.join(appRoot, \"composer.json\"), \"utf8\");\n root = collectRootComponentPackage(JSON.parse(raw), appRoot);\n } catch {\n root = [];\n }\n\n return [...installed, ...root];\n}\n\nconst VIRTUAL_PLUGINS_ID = \"virtual:lattice/plugins\";\nconst RESOLVED_VIRTUAL_PLUGINS_ID = `\\0${VIRTUAL_PLUGINS_ID}`;\n\n/**\n * Exposes the discovered component packages as `virtual:lattice/plugins` — a\n * module whose default export is the array of their `createPlugin(...)` results,\n * ready for `extendRegistry(registry, ...plugins)`. Also grants Vite filesystem\n * access to each package dir so its source compiles from `vendor/` (or a symlink).\n */\nexport function componentPackagesPlugin(packages: LatticeComponentPackage[]): Plugin {\n return {\n name: \"lattice:component-packages\",\n config(config) {\n if (packages.length === 0) {\n return {};\n }\n\n const workspaceRoot = searchForWorkspaceRoot(config.root ?? process.cwd());\n\n return { server: { fs: { allow: [workspaceRoot, ...packages.map((pkg) => pkg.dir)] } } };\n },\n resolveId(id) {\n return id === VIRTUAL_PLUGINS_ID ? RESOLVED_VIRTUAL_PLUGINS_ID : null;\n },\n load(id) {\n if (id !== RESOLVED_VIRTUAL_PLUGINS_ID) {\n return null;\n }\n\n const imports = packages\n .map((pkg, index) => `import p${index} from ${JSON.stringify(pkg.plugin)};`)\n .join(\"\\n\");\n const list = packages.map((_, index) => `p${index}`).join(\", \");\n\n return `${imports}\\nexport default [${list}];\\n`;\n },\n };\n}\n\nexport function latticeConfig(options: LatticeViteOptions = {}): ConfigWithTest {\n const { appRoot, root } = resolveRoots(options);\n\n return {\n resolve: {\n alias: options.source\n ? {\n \"@lattice-php/lattice/css\": path.resolve(root, \"resources/css/lattice.css\"),\n \"@lattice-php/lattice\": path.resolve(root, \"resources/js\"),\n }\n : {\n react: path.resolve(appRoot, \"node_modules/react\"),\n \"react-dom\": path.resolve(appRoot, \"node_modules/react-dom\"),\n },\n dedupe: [\"@inertiajs/react\", \"react\", \"react-dom\"],\n },\n server: options.source\n ? {\n fs: {\n allow: [searchForWorkspaceRoot(appRoot), root],\n },\n }\n : undefined,\n test: {\n server: {\n deps: {\n inline: [\n \"@lattice-php/lattice\",\n /[/\\\\]lattice[/\\\\]dist[/\\\\]/,\n /[/\\\\]lattice[/\\\\]node_modules[/\\\\]@radix-ui[/\\\\]/,\n /[/\\\\]lattice[/\\\\]node_modules[/\\\\]@tiptap[/\\\\]/,\n /[/\\\\]lattice[/\\\\]node_modules[/\\\\]react-i18next[/\\\\]/,\n ],\n },\n },\n },\n };\n}\n\nfunction corePlugin(options: LatticeViteOptions): Plugin {\n return {\n name: \"lattice\",\n config() {\n return latticeConfig(options);\n },\n };\n}\n\nconst OPTIONAL_PEER_STUB_PREFIX = \"\\0lattice-optional-peer/\";\n\n/**\n * Real-time listeners statically import their optional Echo peers. A consumer\n * that never uses real-time should still build, so stub a missing peer with\n * hooks that throw — the `RealtimeListeners` error boundary then degrades\n * gracefully and warns to install the peer, exactly as when it is absent.\n */\nconst OPTIONAL_PEER_STUBS: Record<string, string> = {\n \"@laravel/echo-react\": [\n \"const missing = () => {\",\n \" throw new Error(\",\n ' \"[lattice] Real-time listeners require @laravel/echo-react. Install it and call configureEcho().\",',\n \" );\",\n \"};\",\n \"export const useEcho = missing;\",\n \"export const useEchoPublic = missing;\",\n \"export const useEchoPresence = missing;\",\n \"export const useEchoNotification = missing;\",\n ].join(\"\\n\"),\n};\n\nfunction optionalPeersPlugin(): Plugin {\n return {\n name: \"lattice:optional-peers\",\n enforce: \"pre\",\n async resolveId(id) {\n if (!Object.prototype.hasOwnProperty.call(OPTIONAL_PEER_STUBS, id)) {\n return null;\n }\n\n const installed = await this.resolve(id, undefined, { skipSelf: true });\n\n return installed ? null : `${OPTIONAL_PEER_STUB_PREFIX}${id}`;\n },\n load(id) {\n if (!id.startsWith(OPTIONAL_PEER_STUB_PREFIX)) {\n return null;\n }\n\n return OPTIONAL_PEER_STUBS[id.slice(OPTIONAL_PEER_STUB_PREFIX.length)] ?? null;\n },\n };\n}\n\n/**\n * Refreshes `node.props` typings from the app's own `php artisan\n * lattice:typescript` whenever the dev server starts — installing or updating\n * a component package would otherwise leave its generated types stale until\n * someone remembers to run the command by hand. Dev-server only: a production\n * build machine may not have PHP installed, and the generated file is a dev\n * ergonomics artifact, not a build input.\n *\n * Module-private like its siblings `optionalPeersPlugin`/`corePlugin` — the\n * `refreshTypeScriptTypes` DI seam it defers to lives in\n * `./vite-typescript-refresh`, which isn't part of the published `vite`\n * subpath either (see that module for why).\n */\nfunction typescriptPlugin(options: LatticeViteOptions): Plugin {\n return {\n name: \"lattice:typescript\",\n apply: \"serve\",\n configureServer(server) {\n const typescript = options.typescript ?? true;\n\n if (typescript === false) {\n return;\n }\n\n const { appRoot } = resolveRoots(options);\n\n refreshTypeScriptTypes(appRoot, server.config.logger);\n },\n };\n}\n\nexport function resolveIconOptions(options: LatticeViteOptions): SvgSpriteOptions | null {\n const icons = options.icons ?? true;\n\n if (icons === false) {\n return null;\n }\n\n const { root } = resolveRoots(options);\n const iconOptions = icons === true ? {} : icons;\n const { dirs = [], dts, ...spriteOptions } = iconOptions;\n const defaultTypes = {\n file: \"resources/js/types/sprite-icons.ts\",\n augmentModule: \"@lattice-php/lattice\",\n augmentInterface: \"KnownIcons\",\n };\n\n return {\n ...spriteOptions,\n iconDirs: [path.resolve(root, \"resources/icons\"), ...dirs],\n ...(dts === false ? {} : { dts: { ...defaultTypes, ...dts } }),\n };\n}\n\nfunction resolveRoots(options: LatticeViteOptions): Roots {\n const appRoot = options.appRoot ?? process.cwd();\n const root = options.root ?? path.resolve(appRoot, \"vendor/lattice-php/lattice\");\n\n return { appRoot, root };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vite.js","names":[],"sources":["../resources/js/vite.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { svgSprite } from \"@lattice-php/vite-svg-sprite\";\nimport type { IconTypesOptions, SvgSpriteOptions } from \"@lattice-php/vite-svg-sprite\";\nimport { searchForWorkspaceRoot } from \"vite\";\nimport type { Plugin, PluginOption, UserConfig } from \"vite\";\nimport { refreshTypeScriptTypes } from \"./vite-typescript-refresh\";\n\ntype InlineDependency = string | RegExp;\n\ntype ConfigWithTest = UserConfig & {\n test?: {\n server?: {\n deps?: {\n inline?: InlineDependency[];\n };\n };\n };\n};\n\nexport type LatticeViteIconsOptions = Omit<SvgSpriteOptions, \"dts\" | \"iconDirs\"> & {\n dirs?: string[];\n dts?: Partial<IconTypesOptions> | false;\n};\n\nexport type LatticeViteOptions = {\n appRoot?: string;\n icons?: boolean | LatticeViteIconsOptions;\n root?: string;\n source?: boolean;\n /** Refresh generated TypeScript types via the dev server. Defaults to `true`. */\n typescript?: boolean;\n};\n\ntype Roots = {\n appRoot: string;\n root: string;\n};\n\nexport function lattice(options: LatticeViteOptions = {}): PluginOption[] {\n const { appRoot } = resolveRoots(options);\n const plugins: PluginOption[] = [\n corePlugin(options),\n optionalPeersPlugin(),\n componentPackagesPlugin(discoverComponentPackages(appRoot)),\n typescriptPlugin(options),\n ];\n const iconOptions = resolveIconOptions(options);\n\n if (iconOptions) {\n plugins.push(svgSprite(iconOptions));\n }\n\n return plugins;\n}\n\n/** A Composer package that contributes a Lattice component plugin. */\nexport type LatticeComponentPackage = {\n name: string;\n /** Absolute path to the package's installed directory. */\n dir: string;\n /** Absolute path to the package's JS plugin entry (its `createPlugin(...)`). */\n plugin: string;\n};\n\ntype InstalledPackage = {\n name: string;\n \"install-path\"?: string;\n extra?: { lattice?: { plugin?: string } };\n};\n\ntype RootPackageJson = {\n name?: string;\n extra?: { lattice?: { plugin?: string } };\n};\n\n/**\n * Resolve every Composer package that declares `extra.lattice.plugin` into an\n * absolute plugin-entry path. `installPathsRelativeTo` is `vendor/composer` (the\n * dir `installed.json` records its `install-path`s against).\n */\nexport function collectComponentPackages(\n installed: { packages?: InstalledPackage[] } | InstalledPackage[],\n installPathsRelativeTo: string,\n): LatticeComponentPackage[] {\n const packages = Array.isArray(installed) ? installed : (installed.packages ?? []);\n\n return packages.flatMap((pkg) => {\n const entry = pkg.extra?.lattice?.plugin;\n\n if (typeof entry !== \"string\") {\n return [];\n }\n\n const dir = path.resolve(installPathsRelativeTo, pkg[\"install-path\"] ?? `../${pkg.name}`);\n\n return [{ name: pkg.name, dir, plugin: path.resolve(dir, entry) }];\n });\n}\n\n/**\n * Resolve the composer ROOT project's own `extra.lattice.plugin` — Composer\n * never lists the root package in `installed.json`, so a component package\n * declaring the plugin entry in its own composer.json would otherwise be\n * invisible to its own dev server (e.g. inside a testbench workbench, where\n * the package itself is the app root).\n */\nexport function collectRootComponentPackage(\n composerJson: RootPackageJson,\n appRoot: string,\n): LatticeComponentPackage[] {\n const entry = composerJson.extra?.lattice?.plugin;\n\n if (typeof entry !== \"string\" || typeof composerJson.name !== \"string\") {\n return [];\n }\n\n return [{ name: composerJson.name, dir: appRoot, plugin: path.resolve(appRoot, entry) }];\n}\n\n/**\n * Read `<appRoot>/vendor/composer/installed.json` and `<appRoot>/composer.json`\n * and collect every component package they contribute.\n */\nexport function discoverComponentPackages(appRoot: string): LatticeComponentPackage[] {\n const composerDir = path.resolve(appRoot, \"vendor/composer\");\n\n let installed: LatticeComponentPackage[] = [];\n\n try {\n const raw = readFileSync(path.join(composerDir, \"installed.json\"), \"utf8\");\n installed = collectComponentPackages(JSON.parse(raw), composerDir);\n } catch {\n installed = [];\n }\n\n let root: LatticeComponentPackage[] = [];\n\n try {\n const raw = readFileSync(path.join(appRoot, \"composer.json\"), \"utf8\");\n root = collectRootComponentPackage(JSON.parse(raw), appRoot);\n } catch {\n root = [];\n }\n\n return [...installed, ...root];\n}\n\nconst VIRTUAL_PLUGINS_ID = \"virtual:lattice/plugins\";\nconst RESOLVED_VIRTUAL_PLUGINS_ID = `\\0${VIRTUAL_PLUGINS_ID}`;\n\n/**\n * Exposes the discovered component packages as `virtual:lattice/plugins` — a\n * module whose default export is the array of their `createPlugin(...)` results,\n * ready for `extendRegistry(registry, ...plugins)`. Also grants Vite filesystem\n * access to each package dir so its source compiles from `vendor/` (or a symlink).\n */\nexport function componentPackagesPlugin(packages: LatticeComponentPackage[]): Plugin {\n return {\n name: \"lattice:component-packages\",\n config(config) {\n if (packages.length === 0) {\n return {};\n }\n\n const workspaceRoot = searchForWorkspaceRoot(config.root ?? process.cwd());\n\n return { server: { fs: { allow: [workspaceRoot, ...packages.map((pkg) => pkg.dir)] } } };\n },\n resolveId(id) {\n return id === VIRTUAL_PLUGINS_ID ? RESOLVED_VIRTUAL_PLUGINS_ID : null;\n },\n load(id) {\n if (id !== RESOLVED_VIRTUAL_PLUGINS_ID) {\n return null;\n }\n\n const imports = packages\n .map((pkg, index) => `import p${index} from ${JSON.stringify(pkg.plugin)};`)\n .join(\"\\n\");\n const list = packages.map((_, index) => `p${index}`).join(\", \");\n\n return `${imports}\\nexport default [${list}];\\n`;\n },\n };\n}\n\nexport function latticeConfig(options: LatticeViteOptions = {}): ConfigWithTest {\n const { appRoot, root } = resolveRoots(options);\n\n return {\n resolve: {\n // A react alias would break SSR: Vite only externalizes bare specifiers,\n // so an absolute path inlines react's CJS into the SSR module runner.\n // `dedupe` alone keeps the app on a single React copy, symlinks included.\n ...(options.source\n ? {\n alias: {\n \"@lattice-php/lattice/css\": path.resolve(root, \"resources/css/lattice.css\"),\n \"@lattice-php/lattice\": path.resolve(root, \"resources/js\"),\n },\n }\n : {}),\n dedupe: [\"@inertiajs/react\", \"react\", \"react-dom\"],\n },\n server: options.source\n ? {\n fs: {\n allow: [searchForWorkspaceRoot(appRoot), root],\n },\n }\n : undefined,\n test: {\n server: {\n deps: {\n inline: [\n \"@lattice-php/lattice\",\n /[/\\\\]lattice[/\\\\]dist[/\\\\]/,\n /[/\\\\]lattice[/\\\\]node_modules[/\\\\]@radix-ui[/\\\\]/,\n /[/\\\\]lattice[/\\\\]node_modules[/\\\\]@tiptap[/\\\\]/,\n /[/\\\\]lattice[/\\\\]node_modules[/\\\\]react-i18next[/\\\\]/,\n ],\n },\n },\n },\n };\n}\n\nfunction corePlugin(options: LatticeViteOptions): Plugin {\n return {\n name: \"lattice\",\n config() {\n return latticeConfig(options);\n },\n };\n}\n\nconst OPTIONAL_PEER_STUB_PREFIX = \"\\0lattice-optional-peer/\";\n\n/**\n * Real-time listeners statically import their optional Echo peers. A consumer\n * that never uses real-time should still build, so stub a missing peer with\n * hooks that throw — the `RealtimeListeners` error boundary then degrades\n * gracefully and warns to install the peer, exactly as when it is absent.\n */\nconst OPTIONAL_PEER_STUBS: Record<string, string> = {\n \"@laravel/echo-react\": [\n \"const missing = () => {\",\n \" throw new Error(\",\n ' \"[lattice] Real-time listeners require @laravel/echo-react. Install it and call configureEcho().\",',\n \" );\",\n \"};\",\n \"export const useEcho = missing;\",\n \"export const useEchoPublic = missing;\",\n \"export const useEchoPresence = missing;\",\n \"export const useEchoNotification = missing;\",\n ].join(\"\\n\"),\n};\n\nfunction optionalPeersPlugin(): Plugin {\n return {\n name: \"lattice:optional-peers\",\n enforce: \"pre\",\n async resolveId(id) {\n if (!Object.prototype.hasOwnProperty.call(OPTIONAL_PEER_STUBS, id)) {\n return null;\n }\n\n const installed = await this.resolve(id, undefined, { skipSelf: true });\n\n return installed ? null : `${OPTIONAL_PEER_STUB_PREFIX}${id}`;\n },\n load(id) {\n if (!id.startsWith(OPTIONAL_PEER_STUB_PREFIX)) {\n return null;\n }\n\n return OPTIONAL_PEER_STUBS[id.slice(OPTIONAL_PEER_STUB_PREFIX.length)] ?? null;\n },\n };\n}\n\n/**\n * Refreshes `node.props` typings from the app's own `php artisan\n * lattice:typescript` whenever the dev server starts — installing or updating\n * a component package would otherwise leave its generated types stale until\n * someone remembers to run the command by hand. Dev-server only: a production\n * build machine may not have PHP installed, and the generated file is a dev\n * ergonomics artifact, not a build input.\n *\n * Module-private like its siblings `optionalPeersPlugin`/`corePlugin` — the\n * `refreshTypeScriptTypes` DI seam it defers to lives in\n * `./vite-typescript-refresh`, which isn't part of the published `vite`\n * subpath either (see that module for why).\n */\nfunction typescriptPlugin(options: LatticeViteOptions): Plugin {\n return {\n name: \"lattice:typescript\",\n apply: \"serve\",\n configureServer(server) {\n const typescript = options.typescript ?? true;\n\n if (typescript === false) {\n return;\n }\n\n const { appRoot } = resolveRoots(options);\n\n refreshTypeScriptTypes(appRoot, server.config.logger);\n },\n };\n}\n\nexport function resolveIconOptions(options: LatticeViteOptions): SvgSpriteOptions | null {\n const icons = options.icons ?? true;\n\n if (icons === false) {\n return null;\n }\n\n const { root } = resolveRoots(options);\n const iconOptions = icons === true ? {} : icons;\n const { dirs = [], dts, ...spriteOptions } = iconOptions;\n const defaultTypes = {\n file: \"resources/js/types/sprite-icons.ts\",\n augmentModule: \"@lattice-php/lattice\",\n augmentInterface: \"KnownIcons\",\n };\n\n return {\n ...spriteOptions,\n iconDirs: [path.resolve(root, \"resources/icons\"), ...dirs],\n ...(dts === false ? {} : { dts: { ...defaultTypes, ...dts } }),\n };\n}\n\nfunction resolveRoots(options: LatticeViteOptions): Roots {\n const appRoot = options.appRoot ?? process.cwd();\n const root = options.root ?? path.resolve(appRoot, \"vendor/lattice-php/lattice\");\n\n return { appRoot, root };\n}\n"],"mappings":"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"}
|