@lattice-php/lattice 0.32.0 → 0.34.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.
Files changed (97) hide show
  1. package/dist/appearance/index.d.ts +6 -0
  2. package/dist/appearance/index.js +13 -8
  3. package/dist/appearance/index.js.map +1 -1
  4. package/dist/chat/lib/transport.d.ts +2 -2
  5. package/dist/chat/lib/transport.js +22 -23
  6. package/dist/chat/lib/transport.js.map +1 -1
  7. package/dist/core/api.d.ts +0 -1
  8. package/dist/core/api.js +2 -2
  9. package/dist/core/api.js.map +1 -1
  10. package/dist/core/hooks/column-sizing.d.ts +1 -1
  11. package/dist/core/hooks/column-sizing.js +7 -14
  12. package/dist/core/hooks/column-sizing.js.map +1 -1
  13. package/dist/core/hooks/use-column-resizing.js +22 -54
  14. package/dist/core/hooks/use-column-resizing.js.map +1 -1
  15. package/dist/core/index.d.ts +1 -1
  16. package/dist/core/index.js +2 -2
  17. package/dist/core/registry.d.ts +1 -5
  18. package/dist/core/registry.js +1 -2
  19. package/dist/core/registry.js.map +1 -1
  20. package/dist/core/renderer.js +4 -10
  21. package/dist/core/renderer.js.map +1 -1
  22. package/dist/create-app.d.ts +6 -3
  23. package/dist/create-app.js +13 -5
  24. package/dist/create-app.js.map +1 -1
  25. package/dist/effects/registry.js +1 -4
  26. package/dist/effects/registry.js.map +1 -1
  27. package/dist/event-bridge.d.ts +1 -4
  28. package/dist/event-bridge.js +1 -11
  29. package/dist/event-bridge.js.map +1 -1
  30. package/dist/form/components/fields/choice.js +1 -1
  31. package/dist/form/components/fields/choice.js.map +1 -1
  32. package/dist/form/components/fields/use-flip-reorder.js +3 -2
  33. package/dist/form/components/fields/use-flip-reorder.js.map +1 -1
  34. package/dist/form/components/fields/use-row-collection.js +3 -2
  35. package/dist/form/components/fields/use-row-collection.js.map +1 -1
  36. package/dist/form/hooks/use-form-resolver.js +3 -2
  37. package/dist/form/hooks/use-form-resolver.js.map +1 -1
  38. package/dist/form/types.d.ts +1 -4
  39. package/dist/i18n/config.d.ts +1 -1
  40. package/dist/i18n/config.js +5 -21
  41. package/dist/i18n/config.js.map +1 -1
  42. package/dist/i18n/index.d.ts +2 -2
  43. package/dist/i18n/index.js +2 -2
  44. package/dist/i18n/instance.d.ts +8 -0
  45. package/dist/i18n/instance.js +17 -1
  46. package/dist/i18n/instance.js.map +1 -1
  47. package/dist/i18n/locale-switcher.d.ts +0 -5
  48. package/dist/i18n/locale-switcher.js +1 -5
  49. package/dist/i18n/locale-switcher.js.map +1 -1
  50. package/dist/i18n/locale.js +2 -10
  51. package/dist/i18n/locale.js.map +1 -1
  52. package/dist/i18n/timezone.js +2 -10
  53. package/dist/i18n/timezone.js.map +1 -1
  54. package/dist/icons/icon-renderer.d.ts +1 -2
  55. package/dist/icons/icon-renderer.js +2 -6
  56. package/dist/icons/icon-renderer.js.map +1 -1
  57. package/dist/lib/listeners.d.ts +5 -0
  58. package/dist/lib/listeners.js +19 -0
  59. package/dist/lib/listeners.js.map +1 -0
  60. package/dist/lib/use-layout-effect.d.ts +6 -0
  61. package/dist/lib/use-layout-effect.js +11 -0
  62. package/dist/lib/use-layout-effect.js.map +1 -0
  63. package/dist/ssr.d.ts +20 -0
  64. package/dist/ssr.js +32 -0
  65. package/dist/ssr.js.map +1 -0
  66. package/dist/table/hooks/use-column-visibility.js +4 -12
  67. package/dist/table/hooks/use-column-visibility.js.map +1 -1
  68. package/dist/toast/callout.js +8 -16
  69. package/dist/toast/callout.js.map +1 -1
  70. package/dist/toast/toast.d.ts +2 -0
  71. package/dist/toast/toast.js +16 -10
  72. package/dist/toast/toast.js.map +1 -1
  73. package/dist/ui/segmented-pills.d.ts +1 -1
  74. package/dist/ui/segmented-pills.js +1 -1
  75. package/dist/ui/segmented-pills.js.map +1 -1
  76. package/dist/vite.js +2 -5
  77. package/dist/vite.js.map +1 -1
  78. package/dist-standalone/chunks/{chart-view-WqyMTZiX.js → chart-view-CR1X2aGE.js} +1 -1
  79. package/dist-standalone/chunks/{date-picker-field-CCnkf4SC.js → date-picker-field-B4Xzow-A.js} +1 -1
  80. package/dist-standalone/chunks/{dist-CK9gk4iB.js → dist-ci5ng714.js} +1 -1
  81. package/dist-standalone/chunks/{icon-button-CEwL9uXW.js → icon-button-D3AltHNz.js} +1 -1
  82. package/dist-standalone/chunks/instance-C4O1XWa6.js +1 -0
  83. package/dist-standalone/chunks/locale-GyqtaX1O.js +1 -0
  84. package/dist-standalone/chunks/page-props-BHNNVkgJ.js +1 -0
  85. package/dist-standalone/chunks/{rich-editor-field-DVvEzA3W.js → rich-editor-field-JM3jBEV1.js} +1 -1
  86. package/dist-standalone/chunks/{subscriptions-HzwKFUPT.js → subscriptions-BV4g0s0U.js} +1 -1
  87. package/dist-standalone/chunks/{time-picker-DSs9fDWe.js → time-picker-kbhOHEPd.js} +1 -1
  88. package/dist-standalone/chunks/timezone-CyoBch3a.js +1 -0
  89. package/dist-standalone/chunks/{use-effect-dispatcher-CiWYRvsF.js → use-effect-dispatcher-CJXFkAv1.js} +2 -2
  90. package/dist-standalone/lattice.css +1 -1
  91. package/dist-standalone/lattice.js +4 -4
  92. package/dist-standalone/manifest.json +1 -27
  93. package/package.json +10 -2
  94. package/dist-standalone/chunks/instance-B-C4sgf4.js +0 -1
  95. package/dist-standalone/chunks/locale-CgtcxthU.js +0 -1
  96. package/dist-standalone/chunks/page-props-nxR5n6k4.js +0 -1
  97. package/dist-standalone/chunks/timezone-BORzyZp2.js +0 -1
@@ -6,11 +6,10 @@ function eagerComponent(component) {
6
6
  mode: "eager"
7
7
  };
8
8
  }
9
- function lazyComponent(load, options = {}) {
9
+ function lazyComponent(load) {
10
10
  const erasedLoader = load;
11
11
  return {
12
12
  component: lazy(erasedLoader),
13
- fallback: options.fallback,
14
13
  load: erasedLoader,
15
14
  mode: "lazy"
16
15
  };
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../../resources/js/core/registry.ts"],"sourcesContent":["import { lazy } from \"react\";\nimport type { LazyExoticComponent } from \"react\";\nimport type { RendererComponent, RendererComponentModule } from \"./types\";\nimport type { EffectHandlerRegistry } from \"@lattice-php/lattice/effects/registry\";\nimport type { ColumnRegistry } from \"@lattice-php/lattice/table/registry\";\nimport type { ComponentPropsMap } from \"@lattice-php/lattice/types/generated\";\n\nexport type EagerComponentRegistration = {\n component: RendererComponent;\n mode: \"eager\";\n};\n\nexport type LazyComponentRegistration = {\n component: LazyExoticComponent<RendererComponent>;\n fallback?: RendererComponent;\n load: () => Promise<RendererComponentModule>;\n mode: \"lazy\";\n};\n\nexport type ComponentRegistration = EagerComponentRegistration | LazyComponentRegistration;\n\nexport type ComponentRegistry = Record<string, ComponentRegistration>;\n\nexport type ComponentRegistryFor<TTypes extends keyof ComponentPropsMap & string> = Record<\n TTypes,\n ComponentRegistration\n>;\n\nexport type PluginI18n = {\n /** i18next namespace the plugin's components translate under. */\n namespace: string;\n};\n\nexport type Plugin = {\n name: string;\n components?: ComponentRegistry;\n columns?: ColumnRegistry;\n effects?: EffectHandlerRegistry;\n i18n?: PluginI18n;\n};\n\nexport type Registry = {\n components: ComponentRegistry;\n columns: ColumnRegistry;\n effects: EffectHandlerRegistry;\n};\n\nexport type LazyComponentOptions<TType extends string> = {\n fallback?: RendererComponent<TType>;\n};\n\nexport function eagerComponent<TType extends string>(\n component: RendererComponent<TType>,\n): EagerComponentRegistration {\n return {\n component: component as unknown as RendererComponent,\n mode: \"eager\",\n };\n}\n\nexport function lazyComponent<TType extends string>(\n load: () => Promise<RendererComponentModule<TType>>,\n options: LazyComponentOptions<TType> = {},\n): LazyComponentRegistration {\n const erasedLoader = load as unknown as () => Promise<RendererComponentModule>;\n\n return {\n component: lazy(erasedLoader),\n fallback: options.fallback as RendererComponent | undefined,\n load: erasedLoader,\n mode: \"lazy\",\n };\n}\n\nexport function createPlugin(plugin: Plugin): Plugin {\n return plugin;\n}\n\nexport function createRegistry(...plugins: Plugin[]): Registry {\n return plugins.reduce<Registry>(\n (registry, plugin) => ({\n components: { ...registry.components, ...plugin.components },\n columns: { ...registry.columns, ...plugin.columns },\n effects: { ...registry.effects, ...plugin.effects },\n }),\n { components: {}, columns: {}, effects: {} },\n );\n}\n\nexport function extendRegistry(registry: Registry, ...plugins: Plugin[]): Registry {\n const merged = createRegistry(...plugins);\n\n return {\n components: { ...registry.components, ...merged.components },\n columns: { ...registry.columns, ...merged.columns },\n effects: { ...registry.effects, ...merged.effects },\n };\n}\n"],"mappings":";;AAmDA,SAAgB,eACd,WAC4B;CAC5B,OAAO;EACM;EACX,MAAM;CACR;AACF;AAEA,SAAgB,cACd,MACA,UAAuC,CAAC,GACb;CAC3B,MAAM,eAAe;CAErB,OAAO;EACL,WAAW,KAAK,YAAY;EAC5B,UAAU,QAAQ;EAClB,MAAM;EACN,MAAM;CACR;AACF;AAEA,SAAgB,aAAa,QAAwB;CACnD,OAAO;AACT;AAEA,SAAgB,eAAe,GAAG,SAA6B;CAC7D,OAAO,QAAQ,QACZ,UAAU,YAAY;EACrB,YAAY;GAAE,GAAG,SAAS;GAAY,GAAG,OAAO;EAAW;EAC3D,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,OAAO;EAAQ;EAClD,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,OAAO;EAAQ;CACpD,IACA;EAAE,YAAY,CAAC;EAAG,SAAS,CAAC;EAAG,SAAS,CAAC;CAAE,CAC7C;AACF;AAEA,SAAgB,eAAe,UAAoB,GAAG,SAA6B;CACjF,MAAM,SAAS,eAAe,GAAG,OAAO;CAExC,OAAO;EACL,YAAY;GAAE,GAAG,SAAS;GAAY,GAAG,OAAO;EAAW;EAC3D,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,OAAO;EAAQ;EAClD,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,OAAO;EAAQ;CACpD;AACF"}
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../resources/js/core/registry.ts"],"sourcesContent":["import { lazy } from \"react\";\nimport type { LazyExoticComponent } from \"react\";\nimport type { RendererComponent, RendererComponentModule } from \"./types\";\nimport type { EffectHandlerRegistry } from \"@lattice-php/lattice/effects/registry\";\nimport type { ColumnRegistry } from \"@lattice-php/lattice/table/registry\";\nimport type { ComponentPropsMap } from \"@lattice-php/lattice/types/generated\";\n\nexport type EagerComponentRegistration = {\n component: RendererComponent;\n mode: \"eager\";\n};\n\nexport type LazyComponentRegistration = {\n component: LazyExoticComponent<RendererComponent>;\n load: () => Promise<RendererComponentModule>;\n mode: \"lazy\";\n};\n\nexport type ComponentRegistration = EagerComponentRegistration | LazyComponentRegistration;\n\nexport type ComponentRegistry = Record<string, ComponentRegistration>;\n\nexport type ComponentRegistryFor<TTypes extends keyof ComponentPropsMap & string> = Record<\n TTypes,\n ComponentRegistration\n>;\n\nexport type PluginI18n = {\n /** i18next namespace the plugin's components translate under. */\n namespace: string;\n};\n\nexport type Plugin = {\n name: string;\n components?: ComponentRegistry;\n columns?: ColumnRegistry;\n effects?: EffectHandlerRegistry;\n i18n?: PluginI18n;\n};\n\nexport type Registry = {\n components: ComponentRegistry;\n columns: ColumnRegistry;\n effects: EffectHandlerRegistry;\n};\n\nexport function eagerComponent<TType extends string>(\n component: RendererComponent<TType>,\n): EagerComponentRegistration {\n return {\n component: component as unknown as RendererComponent,\n mode: \"eager\",\n };\n}\n\nexport function lazyComponent<TType extends string>(\n load: () => Promise<RendererComponentModule<TType>>,\n): LazyComponentRegistration {\n const erasedLoader = load as unknown as () => Promise<RendererComponentModule>;\n\n return {\n component: lazy(erasedLoader),\n load: erasedLoader,\n mode: \"lazy\",\n };\n}\n\nexport function createPlugin(plugin: Plugin): Plugin {\n return plugin;\n}\n\nexport function createRegistry(...plugins: Plugin[]): Registry {\n return plugins.reduce<Registry>(\n (registry, plugin) => ({\n components: { ...registry.components, ...plugin.components },\n columns: { ...registry.columns, ...plugin.columns },\n effects: { ...registry.effects, ...plugin.effects },\n }),\n { components: {}, columns: {}, effects: {} },\n );\n}\n\nexport function extendRegistry(registry: Registry, ...plugins: Plugin[]): Registry {\n const merged = createRegistry(...plugins);\n\n return {\n components: { ...registry.components, ...merged.components },\n columns: { ...registry.columns, ...merged.columns },\n effects: { ...registry.effects, ...merged.effects },\n };\n}\n"],"mappings":";;AA8CA,SAAgB,eACd,WAC4B;CAC5B,OAAO;EACM;EACX,MAAM;CACR;AACF;AAEA,SAAgB,cACd,MAC2B;CAC3B,MAAM,eAAe;CAErB,OAAO;EACL,WAAW,KAAK,YAAY;EAC5B,MAAM;EACN,MAAM;CACR;AACF;AAEA,SAAgB,aAAa,QAAwB;CACnD,OAAO;AACT;AAEA,SAAgB,eAAe,GAAG,SAA6B;CAC7D,OAAO,QAAQ,QACZ,UAAU,YAAY;EACrB,YAAY;GAAE,GAAG,SAAS;GAAY,GAAG,OAAO;EAAW;EAC3D,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,OAAO;EAAQ;EAClD,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,OAAO;EAAQ;CACpD,IACA;EAAE,YAAY,CAAC;EAAG,SAAS,CAAC;EAAG,SAAS,CAAC;CAAE,CAC7C;AACF;AAEA,SAAgB,eAAe,UAAoB,GAAG,SAA6B;CACjF,MAAM,SAAS,eAAe,GAAG,OAAO;CAExC,OAAO;EACL,YAAY;GAAE,GAAG,SAAS;GAAY,GAAG,OAAO;EAAW;EAC3D,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,OAAO;EAAQ;EAClD,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,OAAO;EAAQ;CACpD;AACF"}
@@ -64,16 +64,10 @@ var NodeRenderer = memo(function NodeRenderer({ node }) {
64
64
  node,
65
65
  children
66
66
  });
67
- if (registration.mode === "lazy") {
68
- const FallbackComponent = registration.fallback;
69
- return /* @__PURE__ */ jsx(Suspense, {
70
- fallback: FallbackComponent ? /* @__PURE__ */ jsx(FallbackComponent, {
71
- node,
72
- children: null
73
- }) : null,
74
- children: renderedComponent
75
- });
76
- }
67
+ if (registration.mode === "lazy") return /* @__PURE__ */ jsx(Suspense, {
68
+ fallback: null,
69
+ children: renderedComponent
70
+ });
77
71
  return renderedComponent;
78
72
  });
79
73
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.js","names":[],"sources":["../../resources/js/core/renderer.tsx"],"sourcesContent":["import { memo, Suspense } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Node } from \"./types\";\nimport { nodeKey } from \"./nodes\";\nimport { useCollapsed } from \"./collapsed-context\";\nimport { useComponentRegistry } from \"./registry-context\";\n\nconst warnedMissingTypes = new Set<string>();\n\nfunction warnMissingComponent(type: string): void {\n if (!import.meta.env.DEV || warnedMissingTypes.has(type)) {\n return;\n }\n\n warnedMissingTypes.add(type);\n console.warn(\n `[lattice] No component registered for node type \"${type}\" — Lattice rendered a fallback ` +\n \"placeholder. Likely causes: your app registry was not passed to \" +\n \"createLatticeApp({ registry }), or the registry key does not match the PHP \" +\n \"#[AsComponent]/AsField type.\",\n );\n}\n\nfunction MissingComponentIcon() {\n return (\n <svg\n aria-hidden=\"true\"\n className=\"size-lt-icon-md shrink-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n >\n <rect height=\"18\" rx=\"2\" strokeDasharray=\"4 3\" width=\"18\" x=\"3\" y=\"3\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n </svg>\n );\n}\n\n/**\n * Fallback for a node whose type has no registered renderer. Always renders a\n * visible, muted marker — icon-only survives tight spots like table cells — so\n * the gap is never invisible. Shows the type inline in development; keeps it\n * screen-reader-only (plus a hover tooltip) in production.\n */\nfunction MissingComponent({ node }: { node: Node }) {\n warnMissingComponent(node.type);\n\n const label = `Missing component: ${node.type}`;\n\n return (\n <span\n className=\"inline-flex items-center gap-1.5 align-middle text-lt-muted-fg\"\n data-lattice-missing-component={node.type}\n title={label}\n >\n <MissingComponentIcon />\n <span className={import.meta.env.DEV ? \"text-sm\" : \"sr-only\"}>{label}</span>\n </span>\n );\n}\n\nexport function Renderer({ nodes }: { nodes: Node[] }): ReactNode {\n return nodes.map((node, index) => <NodeRenderer key={nodeKey(node, index)} node={node} />);\n}\n\nexport function RenderNode({ node }: { node: Node }): ReactNode {\n return <NodeRenderer node={node} />;\n}\n\nconst NodeRenderer = memo(function NodeRenderer({ node }: { node: Node }) {\n const collapsed = useCollapsed();\n const registry = useComponentRegistry();\n const registration = registry[node.type];\n\n if (collapsed && node.props?.hideWhenCollapsed === true) {\n return null;\n }\n\n if (!registration) {\n return <MissingComponent node={node} />;\n }\n\n const Component = registration.component;\n const children = node.schema?.length ? <Renderer nodes={node.schema} /> : null;\n const renderedComponent = <Component node={node}>{children}</Component>;\n\n if (registration.mode === \"lazy\") {\n const FallbackComponent = registration.fallback;\n const fallback = FallbackComponent ? (\n <FallbackComponent node={node}>{null}</FallbackComponent>\n ) : null;\n\n return <Suspense fallback={fallback}>{renderedComponent}</Suspense>;\n }\n\n return renderedComponent;\n});\n"],"mappings":";;;;;;AAuBA,SAAS,uBAAuB;CAC9B,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,WAAU;EACV,MAAK;EACL,QAAO;EACP,eAAc;EACd,gBAAe;EACf,aAAY;EACZ,SAAQ;EARV,UAAA;GAUE,oBAAC,QAAD;IAAM,QAAO;IAAK,IAAG;IAAI,iBAAgB;IAAM,OAAM;IAAK,GAAE;IAAI,GAAE;GAAK,CAAA;GACvE,oBAAC,QAAD,EAAM,GAAE,UAAW,CAAA;GACnB,oBAAC,QAAD,EAAM,GAAE,aAAc,CAAA;EACnB;;AAET;;;;;;;AAQA,SAAS,iBAAiB,EAAE,QAAwB;CAClD,KAA0B;CAE1B,MAAM,QAAQ,sBAAsB,KAAK;CAEzC,OACE,qBAAC,QAAD;EACE,WAAU;EACV,kCAAgC,KAAK;EACrC,OAAO;EAHT,UAAA,CAKE,oBAAC,sBAAD,CAAuB,CAAA,GACvB,oBAAC,QAAD;GAAM,WAA6C;GAAY,UAAA;EAAY,CAAA,CACvE;;AAEV;AAEA,SAAgB,SAAS,EAAE,SAAuC;CAChE,OAAO,MAAM,KAAK,MAAM,UAAU,oBAAC,cAAD,EAA+C,KAAO,GAAnC,QAAQ,MAAM,KAAK,CAAgB,CAAC;AAC3F;AAEA,SAAgB,WAAW,EAAE,QAAmC;CAC9D,OAAO,oBAAC,cAAD,EAAoB,KAAO,CAAA;AACpC;AAEA,IAAM,eAAe,KAAK,SAAS,aAAa,EAAE,QAAwB;CACxE,MAAM,YAAY,aAAa;CAE/B,MAAM,eADW,qBACI,CAAA,CAAS,KAAK;CAEnC,IAAI,aAAa,KAAK,OAAO,sBAAsB,MACjD,OAAO;CAGT,IAAI,CAAC,cACH,OAAO,oBAAC,kBAAD,EAAwB,KAAO,CAAA;CAGxC,MAAM,YAAY,aAAa;CAC/B,MAAM,WAAW,KAAK,QAAQ,SAAS,oBAAC,UAAD,EAAU,OAAO,KAAK,OAAS,CAAA,IAAI;CAC1E,MAAM,oBAAoB,oBAAC,WAAD;EAAiB;EAAO;CAAoB,CAAA;CAEtE,IAAI,aAAa,SAAS,QAAQ;EAChC,MAAM,oBAAoB,aAAa;EAKvC,OAAO,oBAAC,UAAD;GAAoB,UAJV,oBACf,oBAAC,mBAAD;IAAyB;IAAO,UAAA;GAAwB,CAAA,IACtD;GAEkC,UAAA;EAA4B,CAAA;CACpE;CAEA,OAAO;AACT,CAAC"}
1
+ {"version":3,"file":"renderer.js","names":[],"sources":["../../resources/js/core/renderer.tsx"],"sourcesContent":["import { memo, Suspense } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Node } from \"./types\";\nimport { nodeKey } from \"./nodes\";\nimport { useCollapsed } from \"./collapsed-context\";\nimport { useComponentRegistry } from \"./registry-context\";\n\nconst warnedMissingTypes = new Set<string>();\n\nfunction warnMissingComponent(type: string): void {\n if (!import.meta.env.DEV || warnedMissingTypes.has(type)) {\n return;\n }\n\n warnedMissingTypes.add(type);\n console.warn(\n `[lattice] No component registered for node type \"${type}\" — Lattice rendered a fallback ` +\n \"placeholder. Likely causes: your app registry was not passed to \" +\n \"createLatticeApp({ registry }), or the registry key does not match the PHP \" +\n \"#[AsComponent]/AsField type.\",\n );\n}\n\nfunction MissingComponentIcon() {\n return (\n <svg\n aria-hidden=\"true\"\n className=\"size-lt-icon-md shrink-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n >\n <rect height=\"18\" rx=\"2\" strokeDasharray=\"4 3\" width=\"18\" x=\"3\" y=\"3\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n </svg>\n );\n}\n\n/**\n * Fallback for a node whose type has no registered renderer. Always renders a\n * visible, muted marker — icon-only survives tight spots like table cells — so\n * the gap is never invisible. Shows the type inline in development; keeps it\n * screen-reader-only (plus a hover tooltip) in production.\n */\nfunction MissingComponent({ node }: { node: Node }) {\n warnMissingComponent(node.type);\n\n const label = `Missing component: ${node.type}`;\n\n return (\n <span\n className=\"inline-flex items-center gap-1.5 align-middle text-lt-muted-fg\"\n data-lattice-missing-component={node.type}\n title={label}\n >\n <MissingComponentIcon />\n <span className={import.meta.env.DEV ? \"text-sm\" : \"sr-only\"}>{label}</span>\n </span>\n );\n}\n\nexport function Renderer({ nodes }: { nodes: Node[] }): ReactNode {\n return nodes.map((node, index) => <NodeRenderer key={nodeKey(node, index)} node={node} />);\n}\n\nexport function RenderNode({ node }: { node: Node }): ReactNode {\n return <NodeRenderer node={node} />;\n}\n\nconst NodeRenderer = memo(function NodeRenderer({ node }: { node: Node }) {\n const collapsed = useCollapsed();\n const registry = useComponentRegistry();\n const registration = registry[node.type];\n\n if (collapsed && node.props?.hideWhenCollapsed === true) {\n return null;\n }\n\n if (!registration) {\n return <MissingComponent node={node} />;\n }\n\n const Component = registration.component;\n const children = node.schema?.length ? <Renderer nodes={node.schema} /> : null;\n const renderedComponent = <Component node={node}>{children}</Component>;\n\n if (registration.mode === \"lazy\") {\n return <Suspense fallback={null}>{renderedComponent}</Suspense>;\n }\n\n return renderedComponent;\n});\n"],"mappings":";;;;;;AAuBA,SAAS,uBAAuB;CAC9B,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,WAAU;EACV,MAAK;EACL,QAAO;EACP,eAAc;EACd,gBAAe;EACf,aAAY;EACZ,SAAQ;EARV,UAAA;GAUE,oBAAC,QAAD;IAAM,QAAO;IAAK,IAAG;IAAI,iBAAgB;IAAM,OAAM;IAAK,GAAE;IAAI,GAAE;GAAK,CAAA;GACvE,oBAAC,QAAD,EAAM,GAAE,UAAW,CAAA;GACnB,oBAAC,QAAD,EAAM,GAAE,aAAc,CAAA;EACnB;;AAET;;;;;;;AAQA,SAAS,iBAAiB,EAAE,QAAwB;CAClD,KAA0B;CAE1B,MAAM,QAAQ,sBAAsB,KAAK;CAEzC,OACE,qBAAC,QAAD;EACE,WAAU;EACV,kCAAgC,KAAK;EACrC,OAAO;EAHT,UAAA,CAKE,oBAAC,sBAAD,CAAuB,CAAA,GACvB,oBAAC,QAAD;GAAM,WAA6C;GAAY,UAAA;EAAY,CAAA,CACvE;;AAEV;AAEA,SAAgB,SAAS,EAAE,SAAuC;CAChE,OAAO,MAAM,KAAK,MAAM,UAAU,oBAAC,cAAD,EAA+C,KAAO,GAAnC,QAAQ,MAAM,KAAK,CAAgB,CAAC;AAC3F;AAEA,SAAgB,WAAW,EAAE,QAAmC;CAC9D,OAAO,oBAAC,cAAD,EAAoB,KAAO,CAAA;AACpC;AAEA,IAAM,eAAe,KAAK,SAAS,aAAa,EAAE,QAAwB;CACxE,MAAM,YAAY,aAAa;CAE/B,MAAM,eADW,qBACI,CAAA,CAAS,KAAK;CAEnC,IAAI,aAAa,KAAK,OAAO,sBAAsB,MACjD,OAAO;CAGT,IAAI,CAAC,cACH,OAAO,oBAAC,kBAAD,EAAwB,KAAO,CAAA;CAGxC,MAAM,YAAY,aAAa;CAC/B,MAAM,WAAW,KAAK,QAAQ,SAAS,oBAAC,UAAD,EAAU,OAAO,KAAK,OAAS,CAAA,IAAI;CAC1E,MAAM,oBAAoB,oBAAC,WAAD;EAAiB;EAAO;CAAoB,CAAA;CAEtE,IAAI,aAAa,SAAS,QACxB,OAAO,oBAAC,UAAD;EAAU,UAAU;EAAO,UAAA;CAA4B,CAAA;CAGhE,OAAO;AACT,CAAC"}
@@ -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), `LocaleReload` re-fetches the page after
32
- * a locale switch, and every visit carries the locale/timezone headers. The
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;
@@ -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
- const shared = page.props.lattice;
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
- return pending.length > 0 ? /* @__PURE__ */ jsx(AwaitReady, {
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
- }) : shell;
90
+ });
83
91
  }
84
92
  });
85
93
  initializeAppearance();
@@ -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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"}
@@ -35,10 +35,7 @@ var builtinEffectHandlers = {
35
35
  "toggle-sidebar": bridge(LATTICE_EVENT.toggleSidebar)
36
36
  };
37
37
  function mergeEffectHandlers(...registries) {
38
- return registries.reduce((merged, registry) => ({
39
- ...merged,
40
- ...registry
41
- }), {});
38
+ return Object.assign({}, ...registries);
42
39
  }
43
40
  //#endregion
44
41
  export { builtinEffectHandlers, effectHandler, mergeEffectHandlers };
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../../resources/js/effects/registry.ts"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport type {\n EffectOf,\n EffectProps,\n EffectPropsMap,\n EffectPropsOf,\n} from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/core/event-names\";\nimport { setLocale } from \"@lattice-php/lattice/i18n/locale\";\n\nexport type { EffectOf, EffectProps, EffectPropsOf };\n\nexport type EffectHandler<TType extends string = string> = (effect: EffectOf<TType>) => void;\n\nexport type EffectHandlerRegistry = Record<string, EffectHandler>;\n\nexport type EffectHandlerRegistryFor<TTypes extends keyof EffectPropsMap & string> = Record<\n TTypes,\n EffectHandler\n>;\n\n/**\n * Author a handler against `EffectHandler<\"my.type\">` for a typed payload, then\n * register it through this — it erases the type parameter for the loose registry.\n */\nexport function effectHandler<TType extends string>(\n _type: TType,\n fn: EffectHandler<TType>,\n): EffectHandler {\n return fn as unknown as EffectHandler;\n}\n\nfunction triggerDownload(url: string): void {\n const link = document.createElement(\"a\");\n link.href = url;\n link.rel = \"noopener\";\n document.body.appendChild(link);\n link.click();\n link.remove();\n}\n\nfunction bridge<TType extends keyof EffectPropsMap & string>(event: string): EffectHandler<TType> {\n return (effect) => window.dispatchEvent(new CustomEvent(event, { detail: effect.props }));\n}\n\n/**\n * Imperative effects act directly; the rest bridge to the `lattice:*` DOM events\n * that toast/callout/modal/fragment/form subscribe to. The export cast erases\n * the mapped type to the loose registry shape — the same variance erasure\n * effectHandler performs.\n */\nconst typedBuiltinHandlers: { [K in keyof EffectPropsMap]: EffectHandler<K> } = {\n \"reload-page\": (effect) => (effect.props.full ? window.location.reload() : router.reload()),\n redirect: (effect) => router.visit(effect.props.url),\n download: (effect) => triggerDownload(effect.props.url),\n \"locale-change\": (effect) => setLocale(effect.props.locale),\n toast: bridge<\"toast\">(LATTICE_EVENT.toast),\n callout: bridge<\"callout\">(LATTICE_EVENT.callout),\n \"retract-callout\": bridge<\"retract-callout\">(LATTICE_EVENT.retractCallout),\n \"reload-component\": bridge<\"reload-component\">(LATTICE_EVENT.reloadComponent),\n \"open-modal\": bridge<\"open-modal\">(LATTICE_EVENT.openModal),\n \"close-modal\": bridge<\"close-modal\">(LATTICE_EVENT.closeModal),\n \"reset-form\": bridge<\"reset-form\">(LATTICE_EVENT.resetForm),\n \"toggle-sidebar\": bridge<\"toggle-sidebar\">(LATTICE_EVENT.toggleSidebar),\n};\n\nexport const builtinEffectHandlers: EffectHandlerRegistryFor<keyof EffectPropsMap & string> =\n typedBuiltinHandlers as EffectHandlerRegistryFor<keyof EffectPropsMap & string>;\n\nexport function mergeEffectHandlers(\n ...registries: Array<EffectHandlerRegistry | undefined>\n): EffectHandlerRegistry {\n return registries.reduce<EffectHandlerRegistry>(\n (merged, registry) => ({ ...merged, ...registry }),\n {},\n );\n}\n"],"mappings":";;;;;;;;AAyBA,SAAgB,cACd,OACA,IACe;CACf,OAAO;AACT;AAEA,SAAS,gBAAgB,KAAmB;CAC1C,MAAM,OAAO,SAAS,cAAc,GAAG;CACvC,KAAK,OAAO;CACZ,KAAK,MAAM;CACX,SAAS,KAAK,YAAY,IAAI;CAC9B,KAAK,MAAM;CACX,KAAK,OAAO;AACd;AAEA,SAAS,OAAoD,OAAqC;CAChG,QAAQ,WAAW,OAAO,cAAc,IAAI,YAAY,OAAO,EAAE,QAAQ,OAAO,MAAM,CAAC,CAAC;AAC1F;AAuBA,IAAa,wBACX;CAfA,gBAAgB,WAAY,OAAO,MAAM,OAAO,OAAO,SAAS,OAAO,IAAI,OAAO,OAAO;CACzF,WAAW,WAAW,OAAO,MAAM,OAAO,MAAM,GAAG;CACnD,WAAW,WAAW,gBAAgB,OAAO,MAAM,GAAG;CACtD,kBAAkB,WAAW,UAAU,OAAO,MAAM,MAAM;CAC1D,OAAO,OAAgB,cAAc,KAAK;CAC1C,SAAS,OAAkB,cAAc,OAAO;CAChD,mBAAmB,OAA0B,cAAc,cAAc;CACzE,oBAAoB,OAA2B,cAAc,eAAe;CAC5E,cAAc,OAAqB,cAAc,SAAS;CAC1D,eAAe,OAAsB,cAAc,UAAU;CAC7D,cAAc,OAAqB,cAAc,SAAS;CAC1D,kBAAkB,OAAyB,cAAc,aAAa;AAItE;AAEF,SAAgB,oBACd,GAAG,YACoB;CACvB,OAAO,WAAW,QACf,QAAQ,cAAc;EAAE,GAAG;EAAQ,GAAG;CAAS,IAChD,CAAC,CACH;AACF"}
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../resources/js/effects/registry.ts"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport type {\n EffectOf,\n EffectProps,\n EffectPropsMap,\n EffectPropsOf,\n} from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/core/event-names\";\nimport { setLocale } from \"@lattice-php/lattice/i18n/locale\";\n\nexport type { EffectOf, EffectProps, EffectPropsOf };\n\nexport type EffectHandler<TType extends string = string> = (effect: EffectOf<TType>) => void;\n\nexport type EffectHandlerRegistry = Record<string, EffectHandler>;\n\nexport type EffectHandlerRegistryFor<TTypes extends keyof EffectPropsMap & string> = Record<\n TTypes,\n EffectHandler\n>;\n\n/**\n * Author a handler against `EffectHandler<\"my.type\">` for a typed payload, then\n * register it through this — it erases the type parameter for the loose registry.\n */\nexport function effectHandler<TType extends string>(\n _type: TType,\n fn: EffectHandler<TType>,\n): EffectHandler {\n return fn as unknown as EffectHandler;\n}\n\nfunction triggerDownload(url: string): void {\n const link = document.createElement(\"a\");\n link.href = url;\n link.rel = \"noopener\";\n document.body.appendChild(link);\n link.click();\n link.remove();\n}\n\nfunction bridge<TType extends keyof EffectPropsMap & string>(event: string): EffectHandler<TType> {\n return (effect) => window.dispatchEvent(new CustomEvent(event, { detail: effect.props }));\n}\n\n/**\n * Imperative effects act directly; the rest bridge to the `lattice:*` DOM events\n * that toast/callout/modal/fragment/form subscribe to. The export cast erases\n * the mapped type to the loose registry shape — the same variance erasure\n * effectHandler performs.\n */\nconst typedBuiltinHandlers: { [K in keyof EffectPropsMap]: EffectHandler<K> } = {\n \"reload-page\": (effect) => (effect.props.full ? window.location.reload() : router.reload()),\n redirect: (effect) => router.visit(effect.props.url),\n download: (effect) => triggerDownload(effect.props.url),\n \"locale-change\": (effect) => setLocale(effect.props.locale),\n toast: bridge<\"toast\">(LATTICE_EVENT.toast),\n callout: bridge<\"callout\">(LATTICE_EVENT.callout),\n \"retract-callout\": bridge<\"retract-callout\">(LATTICE_EVENT.retractCallout),\n \"reload-component\": bridge<\"reload-component\">(LATTICE_EVENT.reloadComponent),\n \"open-modal\": bridge<\"open-modal\">(LATTICE_EVENT.openModal),\n \"close-modal\": bridge<\"close-modal\">(LATTICE_EVENT.closeModal),\n \"reset-form\": bridge<\"reset-form\">(LATTICE_EVENT.resetForm),\n \"toggle-sidebar\": bridge<\"toggle-sidebar\">(LATTICE_EVENT.toggleSidebar),\n};\n\nexport const builtinEffectHandlers: EffectHandlerRegistryFor<keyof EffectPropsMap & string> =\n typedBuiltinHandlers as EffectHandlerRegistryFor<keyof EffectPropsMap & string>;\n\nexport function mergeEffectHandlers(\n ...registries: Array<EffectHandlerRegistry | undefined>\n): EffectHandlerRegistry {\n return Object.assign({}, ...registries);\n}\n"],"mappings":";;;;;;;;AAyBA,SAAgB,cACd,OACA,IACe;CACf,OAAO;AACT;AAEA,SAAS,gBAAgB,KAAmB;CAC1C,MAAM,OAAO,SAAS,cAAc,GAAG;CACvC,KAAK,OAAO;CACZ,KAAK,MAAM;CACX,SAAS,KAAK,YAAY,IAAI;CAC9B,KAAK,MAAM;CACX,KAAK,OAAO;AACd;AAEA,SAAS,OAAoD,OAAqC;CAChG,QAAQ,WAAW,OAAO,cAAc,IAAI,YAAY,OAAO,EAAE,QAAQ,OAAO,MAAM,CAAC,CAAC;AAC1F;AAuBA,IAAa,wBACX;CAfA,gBAAgB,WAAY,OAAO,MAAM,OAAO,OAAO,SAAS,OAAO,IAAI,OAAO,OAAO;CACzF,WAAW,WAAW,OAAO,MAAM,OAAO,MAAM,GAAG;CACnD,WAAW,WAAW,gBAAgB,OAAO,MAAM,GAAG;CACtD,kBAAkB,WAAW,UAAU,OAAO,MAAM,MAAM;CAC1D,OAAO,OAAgB,cAAc,KAAK;CAC1C,SAAS,OAAkB,cAAc,OAAO;CAChD,mBAAmB,OAA0B,cAAc,cAAc;CACzE,oBAAoB,OAA2B,cAAc,eAAe;CAC5E,cAAc,OAAqB,cAAc,SAAS;CAC1D,eAAe,OAAsB,cAAc,UAAU;CAC7D,cAAc,OAAqB,cAAc,SAAS;CAC1D,kBAAkB,OAAyB,cAAc,aAAa;AAItE;AAEF,SAAgB,oBACd,GAAG,YACoB;CACvB,OAAO,OAAO,OAAO,CAAC,GAAG,GAAG,UAAU;AACxC"}
@@ -1,9 +1,6 @@
1
- import { ToastMessage } from './toast/index.js';
2
1
  import { Appearance } from './appearance/index.js';
3
2
  type EventBridgeProps = {
4
3
  onAppearanceChange?: (appearance: Appearance) => void;
5
- onLocaleChange?: (locale: string) => void;
6
- onToast?: (toast: ToastMessage) => void;
7
4
  };
8
- export declare function EventBridge({ onAppearanceChange, onLocaleChange, onToast }: EventBridgeProps): null;
5
+ export declare function EventBridge({ onAppearanceChange }: EventBridgeProps): null;
9
6
  export {};
@@ -1,22 +1,12 @@
1
1
  import { isAppearance } from "./appearance/index.js";
2
2
  import { LATTICE_EVENT } from "./core/event-names.js";
3
3
  import { useWindowEvent } from "./core/hooks/use-window-event.js";
4
- import { onToast } from "./toast/toast.js";
5
- import { useEffect } from "react";
6
4
  //#region resources/js/event-bridge.tsx
7
- function EventBridge({ onAppearanceChange, onLocaleChange, onToast: onToast$1 }) {
8
- useEffect(() => {
9
- if (!onToast$1) return;
10
- return onToast(onToast$1);
11
- }, [onToast$1]);
5
+ function EventBridge({ onAppearanceChange }) {
12
6
  useWindowEvent(LATTICE_EVENT.appearanceChange, (event) => {
13
7
  const value = event.detail?.value;
14
8
  if (isAppearance(value)) onAppearanceChange?.(value);
15
9
  }, { enabled: Boolean(onAppearanceChange) });
16
- useWindowEvent(LATTICE_EVENT.localeChange, (event) => {
17
- const locale = event.detail?.locale;
18
- if (typeof locale === "string" && locale !== "") onLocaleChange?.(locale);
19
- }, { enabled: Boolean(onLocaleChange) });
20
10
  return null;
21
11
  }
22
12
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"event-bridge.js","names":[],"sources":["../resources/js/event-bridge.tsx"],"sourcesContent":["import { useEffect } from \"react\";\nimport { onToast as subscribeToToasts } from \"@lattice-php/lattice/toast\";\nimport type { ToastMessage } from \"@lattice-php/lattice/toast\";\nimport { useWindowEvent } from \"@lattice-php/lattice/core/hooks/use-window-event\";\nimport { isAppearance, type Appearance } from \"./appearance\";\nimport { LATTICE_EVENT } from \"./core/event-names\";\n\ntype EventBridgeProps = {\n onAppearanceChange?: (appearance: Appearance) => void;\n onLocaleChange?: (locale: string) => void;\n onToast?: (toast: ToastMessage) => void;\n};\n\ntype AppearanceEvent = CustomEvent<{\n value?: unknown;\n}>;\n\ntype LocaleEvent = CustomEvent<{\n locale?: unknown;\n}>;\n\nexport function EventBridge({ onAppearanceChange, onLocaleChange, onToast }: EventBridgeProps) {\n useEffect(() => {\n if (!onToast) {\n return;\n }\n\n return subscribeToToasts(onToast);\n }, [onToast]);\n\n useWindowEvent(\n LATTICE_EVENT.appearanceChange,\n (event) => {\n const value = (event as AppearanceEvent).detail?.value;\n\n if (isAppearance(value)) {\n onAppearanceChange?.(value);\n }\n },\n { enabled: Boolean(onAppearanceChange) },\n );\n\n useWindowEvent(\n LATTICE_EVENT.localeChange,\n (event) => {\n const locale = (event as LocaleEvent).detail?.locale;\n\n if (typeof locale === \"string\" && locale !== \"\") {\n onLocaleChange?.(locale);\n }\n },\n { enabled: Boolean(onLocaleChange) },\n );\n\n return null;\n}\n"],"mappings":";;;;;;AAqBA,SAAgB,YAAY,EAAE,oBAAoB,gBAAgB,SAAA,aAA6B;CAC7F,gBAAgB;EACd,IAAI,CAAC,WACH;EAGF,OAAO,QAAkB,SAAO;CAClC,GAAG,CAAC,SAAO,CAAC;CAEZ,eACE,cAAc,mBACb,UAAU;EACT,MAAM,QAAS,MAA0B,QAAQ;EAEjD,IAAI,aAAa,KAAK,GACpB,qBAAqB,KAAK;CAE9B,GACA,EAAE,SAAS,QAAQ,kBAAkB,EAAE,CACzC;CAEA,eACE,cAAc,eACb,UAAU;EACT,MAAM,SAAU,MAAsB,QAAQ;EAE9C,IAAI,OAAO,WAAW,YAAY,WAAW,IAC3C,iBAAiB,MAAM;CAE3B,GACA,EAAE,SAAS,QAAQ,cAAc,EAAE,CACrC;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"event-bridge.js","names":[],"sources":["../resources/js/event-bridge.tsx"],"sourcesContent":["import { useWindowEvent } from \"@lattice-php/lattice/core/hooks/use-window-event\";\nimport { isAppearance, type Appearance } from \"./appearance\";\nimport { LATTICE_EVENT } from \"./core/event-names\";\n\ntype EventBridgeProps = {\n onAppearanceChange?: (appearance: Appearance) => void;\n};\n\ntype AppearanceEvent = CustomEvent<{\n value?: unknown;\n}>;\n\nexport function EventBridge({ onAppearanceChange }: EventBridgeProps) {\n useWindowEvent(\n LATTICE_EVENT.appearanceChange,\n (event) => {\n const value = (event as AppearanceEvent).detail?.value;\n\n if (isAppearance(value)) {\n onAppearanceChange?.(value);\n }\n },\n { enabled: Boolean(onAppearanceChange) },\n );\n\n return null;\n}\n"],"mappings":";;;;AAYA,SAAgB,YAAY,EAAE,sBAAwC;CACpE,eACE,cAAc,mBACb,UAAU;EACT,MAAM,QAAS,MAA0B,QAAQ;EAEjD,IAAI,aAAa,KAAK,GACpB,qBAAqB,KAAK;CAE9B,GACA,EAAE,SAAS,QAAQ,kBAAkB,EAAE,CACzC;CAEA,OAAO;AACT"}
@@ -29,7 +29,7 @@ var ChoiceComponent = ({ node }) => {
29
29
  ariaLabel: node.props.label ?? void 0,
30
30
  autoFocus: node.props.autoFocus ?? void 0,
31
31
  disabled: readOnly || disabled,
32
- name: testId,
32
+ name: testId ?? "segment",
33
33
  onSelect: commit,
34
34
  options,
35
35
  tabIndex: node.props.tabIndex ?? void 0,
@@ -1 +1 @@
1
- {"version":3,"file":"choice.js","names":[],"sources":["../../../../resources/js/form/components/fields/choice.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { Option, RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { SegmentedPills } from \"@lattice-php/lattice/ui/segmented-pills\";\nimport { FormFieldFrame } from \"@lattice-php/lattice/form/components/base/field\";\nimport { useControlledField } from \"@lattice-php/lattice/form/hooks/use-controlled-field\";\nimport { useResolvedNode } from \"@lattice-php/lattice/form/hooks/resolved-nodes\";\nimport { useSeedDefault } from \"@lattice-php/lattice/form/hooks/use-seed-default\";\n\nexport const ChoiceComponent: RendererComponent<\"field.choice\"> = ({ node }) => {\n const resolvedNode = useResolvedNode(node);\n const { localName, name, testId, value, error, hidden, required, readOnly, disabled, commit } =\n useControlledField(node);\n const options = useMemo(\n () => (resolvedNode.props as { options?: Option[] }).options ?? [],\n [resolvedNode.props],\n );\n const fallbackValue = options[0]?.value ?? \"\";\n const selected = value || fallbackValue;\n\n useSeedDefault(localName, selected || undefined);\n\n if (hidden || options.length === 0) {\n return null;\n }\n\n return (\n <FormFieldFrame\n error={error}\n helperText={node.props.helperText ?? undefined}\n tooltip={node.props.tooltip ?? undefined}\n label={node.props.label ?? \"\"}\n name={name}\n required={required}\n >\n <input name={name} type=\"hidden\" value={selected} />\n <SegmentedPills\n ariaLabel={node.props.label ?? undefined}\n autoFocus={node.props.autoFocus ?? undefined}\n disabled={readOnly || disabled}\n name={testId}\n onSelect={commit}\n options={options}\n tabIndex={node.props.tabIndex ?? undefined}\n value={selected}\n />\n </FormFieldFrame>\n );\n};\n"],"mappings":";;;;;;;;AAQA,IAAa,mBAAsD,EAAE,WAAW;CAC9E,MAAM,eAAe,gBAAgB,IAAI;CACzC,MAAM,EAAE,WAAW,MAAM,QAAQ,OAAO,OAAO,QAAQ,UAAU,UAAU,UAAU,WACnF,mBAAmB,IAAI;CACzB,MAAM,UAAU,cACP,aAAa,MAAiC,WAAW,CAAC,GACjE,CAAC,aAAa,KAAK,CACrB;CACA,MAAM,gBAAgB,QAAQ,EAAE,EAAE,SAAS;CAC3C,MAAM,WAAW,SAAS;CAE1B,eAAe,WAAW,YAAY,KAAA,CAAS;CAE/C,IAAI,UAAU,QAAQ,WAAW,GAC/B,OAAO;CAGT,OACE,qBAAC,gBAAD;EACS;EACP,YAAY,KAAK,MAAM,cAAc,KAAA;EACrC,SAAS,KAAK,MAAM,WAAW,KAAA;EAC/B,OAAO,KAAK,MAAM,SAAS;EACrB;EACI;EANZ,UAAA,CAQE,oBAAC,SAAD;GAAa;GAAM,MAAK;GAAS,OAAO;EAAW,CAAA,GACnD,oBAAC,gBAAD;GACE,WAAW,KAAK,MAAM,SAAS,KAAA;GAC/B,WAAW,KAAK,MAAM,aAAa,KAAA;GACnC,UAAU,YAAY;GACtB,MAAM;GACN,UAAU;GACD;GACT,UAAU,KAAK,MAAM,YAAY,KAAA;GACjC,OAAO;EACR,CAAA,CACa;;AAEpB"}
1
+ {"version":3,"file":"choice.js","names":[],"sources":["../../../../resources/js/form/components/fields/choice.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { Option, RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { SegmentedPills } from \"@lattice-php/lattice/ui/segmented-pills\";\nimport { FormFieldFrame } from \"@lattice-php/lattice/form/components/base/field\";\nimport { useControlledField } from \"@lattice-php/lattice/form/hooks/use-controlled-field\";\nimport { useResolvedNode } from \"@lattice-php/lattice/form/hooks/resolved-nodes\";\nimport { useSeedDefault } from \"@lattice-php/lattice/form/hooks/use-seed-default\";\n\nexport const ChoiceComponent: RendererComponent<\"field.choice\"> = ({ node }) => {\n const resolvedNode = useResolvedNode(node);\n const { localName, name, testId, value, error, hidden, required, readOnly, disabled, commit } =\n useControlledField(node);\n const options = useMemo(\n () => (resolvedNode.props as { options?: Option[] }).options ?? [],\n [resolvedNode.props],\n );\n const fallbackValue = options[0]?.value ?? \"\";\n const selected = value || fallbackValue;\n\n useSeedDefault(localName, selected || undefined);\n\n if (hidden || options.length === 0) {\n return null;\n }\n\n return (\n <FormFieldFrame\n error={error}\n helperText={node.props.helperText ?? undefined}\n tooltip={node.props.tooltip ?? undefined}\n label={node.props.label ?? \"\"}\n name={name}\n required={required}\n >\n <input name={name} type=\"hidden\" value={selected} />\n <SegmentedPills\n ariaLabel={node.props.label ?? undefined}\n autoFocus={node.props.autoFocus ?? undefined}\n disabled={readOnly || disabled}\n name={testId ?? \"segment\"}\n onSelect={commit}\n options={options}\n tabIndex={node.props.tabIndex ?? undefined}\n value={selected}\n />\n </FormFieldFrame>\n );\n};\n"],"mappings":";;;;;;;;AAQA,IAAa,mBAAsD,EAAE,WAAW;CAC9E,MAAM,eAAe,gBAAgB,IAAI;CACzC,MAAM,EAAE,WAAW,MAAM,QAAQ,OAAO,OAAO,QAAQ,UAAU,UAAU,UAAU,WACnF,mBAAmB,IAAI;CACzB,MAAM,UAAU,cACP,aAAa,MAAiC,WAAW,CAAC,GACjE,CAAC,aAAa,KAAK,CACrB;CACA,MAAM,gBAAgB,QAAQ,EAAE,EAAE,SAAS;CAC3C,MAAM,WAAW,SAAS;CAE1B,eAAe,WAAW,YAAY,KAAA,CAAS;CAE/C,IAAI,UAAU,QAAQ,WAAW,GAC/B,OAAO;CAGT,OACE,qBAAC,gBAAD;EACS;EACP,YAAY,KAAK,MAAM,cAAc,KAAA;EACrC,SAAS,KAAK,MAAM,WAAW,KAAA;EAC/B,OAAO,KAAK,MAAM,SAAS;EACrB;EACI;EANZ,UAAA,CAQE,oBAAC,SAAD;GAAa;GAAM,MAAK;GAAS,OAAO;EAAW,CAAA,GACnD,oBAAC,gBAAD;GACE,WAAW,KAAK,MAAM,SAAS,KAAA;GAC/B,WAAW,KAAK,MAAM,aAAa,KAAA;GACnC,UAAU,YAAY;GACtB,MAAM,UAAU;GAChB,UAAU;GACD;GACT,UAAU,KAAK,MAAM,YAAY,KAAA;GACjC,OAAO;EACR,CAAA,CACa;;AAEpB"}
@@ -1,4 +1,5 @@
1
- import { useCallback, useLayoutEffect, useRef } from "react";
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, useLayoutEffect, useRef } from \"react\";\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":";;AAEA,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,sBAAsB;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
+ {"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 { useCallback, useLayoutEffect } from "react";
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, useLayoutEffect } from \"react\";\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":";;;;;AAuBA,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,sBAAsB;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
+ {"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, useLayoutEffect, useMemo, useRef, useState } from "react";
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, useLayoutEffect, useMemo, useRef, useState } from \"react\";\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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"}
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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"}
@@ -1,7 +1,4 @@
1
- import { Method } from '@inertiajs/core';
2
1
  import { NodeUnionOf } from '../core/types.js';
3
- import { FormFieldNodeType, FormNodeType } from '../types/generated.js';
2
+ import { FormNodeType } from '../types/generated.js';
4
3
  export type { Checkbox, Choice, DateInput, DateTimeInput, Form, FormNodeType, HiddenInput, LabelAction, NumberInput, PasswordInput, RichEditor, Select, Textarea, TextInput, TimeInput, } from '../types/generated.js';
5
- export type FormFieldNode = NodeUnionOf<FormFieldNodeType>;
6
4
  export type FormNode = NodeUnionOf<FormNodeType>;
7
- export type FormMethod = Method;
@@ -3,8 +3,8 @@ type Config = {
3
3
  readonly locales: readonly string[];
4
4
  readonly timezone: string | null;
5
5
  };
6
+ export declare const subscribeConfig: (callback: () => void) => () => void;
6
7
  export declare function setConfig(config: I18nConfig | undefined): void;
7
8
  export declare function configTimezone(): string | null;
8
- export declare function subscribeConfig(callback: () => void): () => void;
9
9
  export declare function useConfig(): Config;
10
10
  export {};
@@ -1,33 +1,23 @@
1
+ import { createListeners } from "../lib/listeners.js";
1
2
  import { useSyncExternalStore } from "react";
2
3
  //#region resources/js/i18n/config.ts
3
4
  var fallback = {
4
5
  locales: [],
5
6
  timezone: null
6
7
  };
7
- var listeners = /* @__PURE__ */ new Set();
8
+ var { subscribe, notify } = createListeners();
8
9
  var active = fallback;
9
10
  function normalizeLocales(locales) {
10
11
  return Array.from(new Set((locales ?? []).map((locale) => locale.trim()).filter(Boolean)));
11
12
  }
12
- function sameLocales(left, right) {
13
- return left.length === right.length && left.every((locale, index) => locale === right[index]);
14
- }
15
13
  function snapshot() {
16
14
  return active;
17
15
  }
18
- function subscribe(callback) {
19
- listeners.add(callback);
20
- return () => {
21
- listeners.delete(callback);
22
- };
23
- }
24
- function notify() {
25
- listeners.forEach((listener) => listener());
26
- }
16
+ var subscribeConfig = subscribe;
27
17
  function setConfig(config) {
28
18
  const locales = normalizeLocales(config?.locales);
29
19
  const timezone = config?.timezone ?? null;
30
- if (sameLocales(active.locales, locales) && active.timezone === timezone) return;
20
+ if (active.locales.join("") === locales.join("") && active.timezone === timezone) return;
31
21
  active = {
32
22
  locales,
33
23
  timezone
@@ -37,14 +27,8 @@ function setConfig(config) {
37
27
  function configTimezone() {
38
28
  return active.timezone;
39
29
  }
40
- function subscribeConfig(callback) {
41
- listeners.add(callback);
42
- return () => {
43
- listeners.delete(callback);
44
- };
45
- }
46
30
  function useConfig() {
47
- return useSyncExternalStore(subscribe, snapshot, () => fallback);
31
+ return useSyncExternalStore(subscribeConfig, snapshot, () => fallback);
48
32
  }
49
33
  //#endregion
50
34
  export { configTimezone, setConfig, subscribeConfig, useConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":[],"sources":["../../resources/js/i18n/config.ts"],"sourcesContent":["import type { I18nConfig } from \"@lattice-php/lattice/types/generated\";\nimport { useSyncExternalStore } from \"react\";\n\ntype Config = {\n readonly locales: readonly string[];\n readonly timezone: string | null;\n};\n\nconst fallback: Config = { locales: [], timezone: null };\nconst listeners = new Set<() => void>();\n\nlet active: Config = fallback;\n\nfunction normalizeLocales(locales: readonly string[] | undefined): string[] {\n return Array.from(new Set((locales ?? []).map((locale) => locale.trim()).filter(Boolean)));\n}\n\nfunction sameLocales(left: readonly string[], right: readonly string[]): boolean {\n return left.length === right.length && left.every((locale, index) => locale === right[index]);\n}\n\nfunction snapshot(): Config {\n return active;\n}\n\nfunction subscribe(callback: () => void): () => void {\n listeners.add(callback);\n\n return () => {\n listeners.delete(callback);\n };\n}\n\nfunction notify(): void {\n listeners.forEach((listener) => listener());\n}\n\nexport function setConfig(config: I18nConfig | undefined): void {\n const locales = normalizeLocales(config?.locales);\n const timezone = config?.timezone ?? null;\n\n if (sameLocales(active.locales, locales) && active.timezone === timezone) {\n return;\n }\n\n active = { locales, timezone };\n notify();\n}\n\nexport function configTimezone(): string | null {\n return active.timezone;\n}\n\nexport function subscribeConfig(callback: () => void): () => void {\n listeners.add(callback);\n\n return () => {\n listeners.delete(callback);\n };\n}\n\nexport function useConfig(): Config {\n return useSyncExternalStore(subscribe, snapshot, () => fallback);\n}\n"],"mappings":";;AAQA,IAAM,WAAmB;CAAE,SAAS,CAAC;CAAG,UAAU;AAAK;AACvD,IAAM,4BAAY,IAAI,IAAgB;AAEtC,IAAI,SAAiB;AAErB,SAAS,iBAAiB,SAAkD;CAC1E,OAAO,MAAM,KAAK,IAAI,KAAK,WAAW,CAAC,EAAA,CAAG,KAAK,WAAW,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC;AAC3F;AAEA,SAAS,YAAY,MAAyB,OAAmC;CAC/E,OAAO,KAAK,WAAW,MAAM,UAAU,KAAK,OAAO,QAAQ,UAAU,WAAW,MAAM,MAAM;AAC9F;AAEA,SAAS,WAAmB;CAC1B,OAAO;AACT;AAEA,SAAS,UAAU,UAAkC;CACnD,UAAU,IAAI,QAAQ;CAEtB,aAAa;EACX,UAAU,OAAO,QAAQ;CAC3B;AACF;AAEA,SAAS,SAAe;CACtB,UAAU,SAAS,aAAa,SAAS,CAAC;AAC5C;AAEA,SAAgB,UAAU,QAAsC;CAC9D,MAAM,UAAU,iBAAiB,QAAQ,OAAO;CAChD,MAAM,WAAW,QAAQ,YAAY;CAErC,IAAI,YAAY,OAAO,SAAS,OAAO,KAAK,OAAO,aAAa,UAC9D;CAGF,SAAS;EAAE;EAAS;CAAS;CAC7B,OAAO;AACT;AAEA,SAAgB,iBAAgC;CAC9C,OAAO,OAAO;AAChB;AAEA,SAAgB,gBAAgB,UAAkC;CAChE,UAAU,IAAI,QAAQ;CAEtB,aAAa;EACX,UAAU,OAAO,QAAQ;CAC3B;AACF;AAEA,SAAgB,YAAoB;CAClC,OAAO,qBAAqB,WAAW,gBAAgB,QAAQ;AACjE"}
1
+ {"version":3,"file":"config.js","names":[],"sources":["../../resources/js/i18n/config.ts"],"sourcesContent":["import type { I18nConfig } from \"@lattice-php/lattice/types/generated\";\nimport { useSyncExternalStore } from \"react\";\nimport { createListeners } from \"@lattice-php/lattice/lib/listeners\";\n\ntype Config = {\n readonly locales: readonly string[];\n readonly timezone: string | null;\n};\n\nconst fallback: Config = { locales: [], timezone: null };\nconst { subscribe, notify } = createListeners();\n\nlet active: Config = fallback;\n\nfunction normalizeLocales(locales: readonly string[] | undefined): string[] {\n return Array.from(new Set((locales ?? []).map((locale) => locale.trim()).filter(Boolean)));\n}\n\nfunction snapshot(): Config {\n return active;\n}\n\nexport const subscribeConfig = subscribe;\n\nexport function setConfig(config: I18nConfig | undefined): void {\n const locales = normalizeLocales(config?.locales);\n const timezone = config?.timezone ?? null;\n\n if (active.locales.join(\"\u001f\") === locales.join(\"\u001f\") && active.timezone === timezone) {\n return;\n }\n\n active = { locales, timezone };\n notify();\n}\n\nexport function configTimezone(): string | null {\n return active.timezone;\n}\n\nexport function useConfig(): Config {\n return useSyncExternalStore(subscribeConfig, snapshot, () => fallback);\n}\n"],"mappings":";;;AASA,IAAM,WAAmB;CAAE,SAAS,CAAC;CAAG,UAAU;AAAK;AACvD,IAAM,EAAE,WAAW,WAAW,gBAAgB;AAE9C,IAAI,SAAiB;AAErB,SAAS,iBAAiB,SAAkD;CAC1E,OAAO,MAAM,KAAK,IAAI,KAAK,WAAW,CAAC,EAAA,CAAG,KAAK,WAAW,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC;AAC3F;AAEA,SAAS,WAAmB;CAC1B,OAAO;AACT;AAEA,IAAa,kBAAkB;AAE/B,SAAgB,UAAU,QAAsC;CAC9D,MAAM,UAAU,iBAAiB,QAAQ,OAAO;CAChD,MAAM,WAAW,QAAQ,YAAY;CAErC,IAAI,OAAO,QAAQ,KAAK,GAAG,MAAM,QAAQ,KAAK,GAAG,KAAK,OAAO,aAAa,UACxE;CAGF,SAAS;EAAE;EAAS;CAAS;CAC7B,OAAO;AACT;AAEA,SAAgB,iBAAgC;CAC9C,OAAO,OAAO;AAChB;AAEA,SAAgB,YAAoB;CAClC,OAAO,qBAAqB,iBAAiB,gBAAgB,QAAQ;AACvE"}
@@ -2,10 +2,10 @@ export { i18n, useT, translate } from './instance.js';
2
2
  export { configureI18n, enableBackend } from './backend.js';
3
3
  export { currentLocale, localeHeader, setLocale, useLocale } from './locale.js';
4
4
  export { LocaleReload } from './locale-reload.js';
5
- export { LocaleSwitcher, useLocaleOptions } from './locale-switcher.js';
5
+ export { useLocaleOptions } from './locale-switcher.js';
6
6
  export { configureI18nFromPageProps, i18nConfigFromPageProps } from './page-props.js';
7
7
  export type { BackendOptions, ConfigureI18nOptions, I18nConfig } from './backend.js';
8
- export type { LocaleOption, LocaleSwitcherProps, UseLocaleOptionsOptions, UseLocaleOptionsReturn, } from './locale-switcher.js';
8
+ export type { LocaleOption, UseLocaleOptionsOptions, UseLocaleOptionsReturn, } from './locale-switcher.js';
9
9
  export type { UseLocaleReturn } from './locale.js';
10
10
  export { currentTimezone, setTimezone, useTimezone } from './timezone.js';
11
11
  export type { UseTimezoneReturn } from './timezone.js';
@@ -4,7 +4,7 @@ import { i18n, translate, useT } from "./instance.js";
4
4
  import { LocaleReload } from "./locale-reload.js";
5
5
  import { i18nConfigFromPageProps } from "./shared-props.js";
6
6
  import { configureI18n, enableBackend } from "./backend.js";
7
- import { LocaleSwitcher, useLocaleOptions } from "./locale-switcher.js";
7
+ import { useLocaleOptions } from "./locale-switcher.js";
8
8
  import { configureI18nFromPageProps } from "./page-props.js";
9
9
  import { DateTime } from "./date-time.js";
10
- export { DateTime, LocaleReload, LocaleSwitcher, configureI18n, configureI18nFromPageProps, currentLocale, currentTimezone, enableBackend, i18n, i18nConfigFromPageProps, localeHeader, setLocale, setTimezone, translate, useLocale, useLocaleOptions, useT, useTimezone };
10
+ export { DateTime, LocaleReload, configureI18n, configureI18nFromPageProps, currentLocale, currentTimezone, enableBackend, i18n, i18nConfigFromPageProps, localeHeader, setLocale, setTimezone, translate, useLocale, useLocaleOptions, useT, useTimezone };
@@ -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