@lattice-php/lattice 0.16.0 → 0.17.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 (122) hide show
  1. package/dist/action/components/action-form.js +1 -1
  2. package/dist/action/use-action.js +1 -1
  3. package/dist/chat/types.d.ts +2 -10
  4. package/dist/core/components/button.js +34 -18
  5. package/dist/core/components/button.js.map +1 -1
  6. package/dist/core/components/control.d.ts +0 -2
  7. package/dist/core/components/control.js.map +1 -1
  8. package/dist/core/components/link.js +25 -29
  9. package/dist/core/components/link.js.map +1 -1
  10. package/dist/core/types.d.ts +22 -42
  11. package/dist/core/use-click-behavior.d.ts +42 -0
  12. package/dist/core/use-click-behavior.js +46 -0
  13. package/dist/core/use-click-behavior.js.map +1 -0
  14. package/dist/create-app.d.ts +8 -2
  15. package/dist/create-app.js +4 -2
  16. package/dist/create-app.js.map +1 -1
  17. package/dist/effects/registry.d.ts +9 -20
  18. package/dist/effects/registry.js +4 -6
  19. package/dist/effects/registry.js.map +1 -1
  20. package/dist/events/event-bridge.d.ts +1 -1
  21. package/dist/events/event-bridge.js +1 -1
  22. package/dist/events/event-bridge.js.map +1 -1
  23. package/dist/form/components/base/input-otp.d.ts +2 -1
  24. package/dist/form/components/base/input-otp.js.map +1 -1
  25. package/dist/form/components/conditions.d.ts +1 -1
  26. package/dist/form/components/conditions.js.map +1 -1
  27. package/dist/form/components/field-props.d.ts +2 -1
  28. package/dist/form/components/field-props.js.map +1 -1
  29. package/dist/form/components/field-scope.d.ts +2 -1
  30. package/dist/form/components/field-scope.js.map +1 -1
  31. package/dist/form/components/fields/row-action-menu.d.ts +2 -1
  32. package/dist/form/components/fields/row-action-menu.js.map +1 -1
  33. package/dist/form/components/fields/row-item.d.ts +2 -1
  34. package/dist/form/components/fields/row-item.js.map +1 -1
  35. package/dist/form/components/fields/table-rows.d.ts +2 -1
  36. package/dist/form/components/fields/table-rows.js.map +1 -1
  37. package/dist/form/components/fields/time-picker-columns.d.ts +2 -1
  38. package/dist/form/components/fields/time-picker-columns.js.map +1 -1
  39. package/dist/form/components/fields/time-picker.d.ts +2 -1
  40. package/dist/form/components/fields/time-picker.js.map +1 -1
  41. package/dist/form/components/fields/use-row-collection.d.ts +2 -1
  42. package/dist/form/components/fields/use-row-collection.js.map +1 -1
  43. package/dist/form/components/form-path.d.ts +0 -1
  44. package/dist/form/components/form-path.js +1 -1
  45. package/dist/form/components/form-path.js.map +1 -1
  46. package/dist/form/components/prefill-targets.d.ts +1 -1
  47. package/dist/form/components/prefill-targets.js.map +1 -1
  48. package/dist/form/components/types.d.ts +5 -1
  49. package/dist/form/components/use-field-commit.d.ts +2 -1
  50. package/dist/form/components/use-field-commit.js.map +1 -1
  51. package/dist/form/components/use-form-resolver.d.ts +2 -1
  52. package/dist/form/components/use-form-resolver.js.map +1 -1
  53. package/dist/format/date-time.d.ts +12 -0
  54. package/dist/format/date-time.js +30 -0
  55. package/dist/format/date-time.js.map +1 -0
  56. package/dist/format/value.js +1 -1
  57. package/dist/format/value.js.map +1 -1
  58. package/dist/i18n/config.d.ts +2 -1
  59. package/dist/i18n/config.js.map +1 -1
  60. package/dist/i18n/date-time.js +1 -1
  61. package/dist/i18n/date-time.js.map +1 -1
  62. package/dist/i18n/instance.d.ts +1 -1
  63. package/dist/i18n/instance.js.map +1 -1
  64. package/dist/i18n/locale-reload.d.ts +2 -1
  65. package/dist/i18n/locale-reload.js.map +1 -1
  66. package/dist/index.js +1 -1
  67. package/dist/layout/components.js +1 -1
  68. package/dist/layout/menu-item.js +43 -49
  69. package/dist/layout/menu-item.js.map +1 -1
  70. package/dist/notifications/api.d.ts +6 -0
  71. package/dist/notifications/api.js +21 -0
  72. package/dist/notifications/api.js.map +1 -0
  73. package/dist/notifications/components/empty-state.d.ts +1 -0
  74. package/dist/notifications/components/empty-state.js +21 -0
  75. package/dist/notifications/components/empty-state.js.map +1 -0
  76. package/dist/notifications/components/notification-item.d.ts +6 -0
  77. package/dist/notifications/components/notification-item.js +65 -0
  78. package/dist/notifications/components/notification-item.js.map +1 -0
  79. package/dist/notifications/components/notification-list.d.ts +10 -0
  80. package/dist/notifications/components/notification-list.js +38 -0
  81. package/dist/notifications/components/notification-list.js.map +1 -0
  82. package/dist/notifications/components/notifications-echo.d.ts +5 -0
  83. package/dist/notifications/components/notifications-echo.js +22 -0
  84. package/dist/notifications/components/notifications-echo.js.map +1 -0
  85. package/dist/notifications/components/notifications.d.ts +3 -0
  86. package/dist/notifications/components/notifications.js +109 -0
  87. package/dist/notifications/components/notifications.js.map +1 -0
  88. package/dist/notifications/index.d.ts +1 -0
  89. package/dist/notifications/index.js +11 -0
  90. package/dist/notifications/index.js.map +1 -0
  91. package/dist/notifications/store.d.ts +25 -0
  92. package/dist/notifications/store.js +100 -0
  93. package/dist/notifications/store.js.map +1 -0
  94. package/dist/notifications/types.d.ts +10 -0
  95. package/dist/notifications/types.js +0 -0
  96. package/dist/realtime/build-effects.d.ts +2 -1
  97. package/dist/realtime/build-effects.js.map +1 -1
  98. package/dist/realtime/subscriptions.js.map +1 -1
  99. package/dist/registry/eager.js +2 -1
  100. package/dist/registry/eager.js.map +1 -1
  101. package/dist/registry/lazy.js +2 -1
  102. package/dist/registry/lazy.js.map +1 -1
  103. package/dist/table/bulk.d.ts +2 -1
  104. package/dist/table/bulk.js.map +1 -1
  105. package/dist/table/components/bulk-bar.js +2 -2
  106. package/dist/table/components/cells/money-cell.js +1 -1
  107. package/dist/table/components/cells/number-cell.js +1 -1
  108. package/dist/table/components/cells/text-cell.js +1 -1
  109. package/dist/table/components/filter-controls.d.ts +0 -4
  110. package/dist/table/components/filter-controls.js.map +1 -1
  111. package/dist/table/components/table-cell.js.map +1 -1
  112. package/dist/table/format.d.ts +1 -12
  113. package/dist/table/format.js +2 -26
  114. package/dist/table/format.js.map +1 -1
  115. package/dist/table/types.d.ts +7 -17
  116. package/dist/types/generated.d.ts +119 -290
  117. package/dist/vite-client.d.ts +8 -0
  118. package/dist/vite.d.ts +35 -1
  119. package/dist/vite.js +60 -2
  120. package/dist/vite.js.map +1 -1
  121. package/package.json +13 -2
  122. package/resources/icons/bell.svg +16 -0
@@ -1,5 +1,9 @@
1
1
  import { Method } from '@inertiajs/core';
2
- export type { Checkbox, Choice, DateInput, DateTimeInput, Form, FormFieldNode, FormNode, FormNodeType, HiddenInput, NumberInput, PasswordInput, RichEditor, Select, Textarea, TextInput, TimeInput, } from '../../types/generated';
2
+ import { NodeUnionOf } from '../../core/types';
3
+ import { FormFieldNodeType, FormNodeType } from '../../types/generated';
4
+ export type { Checkbox, Choice, DateInput, DateTimeInput, Form, FormNodeType, HiddenInput, NumberInput, PasswordInput, RichEditor, Select, Textarea, TextInput, TimeInput, } from '../../types/generated';
5
+ export type FormFieldNode = NodeUnionOf<FormFieldNodeType>;
6
+ export type FormNode = NodeUnionOf<FormNodeType>;
3
7
  export type FormMethod = Method;
4
8
  export type FormLabelAction = {
5
9
  href: string;
@@ -1,4 +1,4 @@
1
- export type FieldCommit = {
1
+ type FieldCommit = {
2
2
  /** Write the value and validate immediately (precognitive) or clear errors. */
3
3
  commit: (name: string, value: unknown) => void;
4
4
  /** Write the value and clear errors, deferring validation to a later `blur`. */
@@ -16,3 +16,4 @@ export type FieldCommit = {
16
16
  * behavior is identical to before.
17
17
  */
18
18
  export declare function useFieldCommit(): FieldCommit;
19
+ export {};
@@ -1 +1 @@
1
- {"version":3,"file":"use-field-commit.js","names":[],"sources":["../../../resources/js/form/components/use-field-commit.ts"],"sourcesContent":["import { useFieldScope } from \"./field-scope\";\nimport { useFormContext } from \"./context\";\nimport { usePrefillController } from \"./prefill-context\";\nimport { useSetFormValue } from \"./values\";\n\nexport type FieldCommit = {\n /** Write the value and validate immediately (precognitive) or clear errors. */\n commit: (name: string, value: unknown) => void;\n /** Write the value and clear errors, deferring validation to a later `blur`. */\n change: (name: string, value: unknown) => void;\n /** Validate the field now if precognitive (e.g. on blur or popover close). */\n blur: (name: string) => void;\n};\n\n/**\n * The shared field-mutation contract every form field uses to write its value\n * and drive precognition. Fields that validate on change call `commit`; fields\n * that validate on blur/close (rich editor, select) call `change` then `blur`.\n *\n * When called inside a `FieldScopeProvider`, writes go through the scope's\n * `setValue` and error paths use the scoped dot-key; outside a scope the\n * behavior is identical to before.\n */\nexport function useFieldCommit(): FieldCommit {\n const { clearErrors, precognitive, validate } = useFormContext();\n const setGlobal = useSetFormValue();\n const scope = useFieldScope();\n const prefill = usePrefillController();\n\n const write = (name: string, value: unknown): void => {\n if (scope) {\n scope.setValue(name, value);\n } else {\n setGlobal(name, value);\n }\n prefill?.markUserEdit(scope ? scope.overrideKey(name) : name);\n };\n const errorPath = (name: string): string => (scope ? scope.errorKey(name) : name);\n\n return {\n commit(name, value) {\n write(name, value);\n if (precognitive) {\n validate(errorPath(name));\n } else {\n clearErrors(errorPath(name));\n }\n },\n change(name, value) {\n write(name, value);\n clearErrors(errorPath(name));\n },\n blur(name) {\n if (precognitive) {\n validate(errorPath(name));\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAuBA,SAAgB,iBAA8B;CAC5C,MAAM,EAAE,aAAa,cAAc,aAAa,eAAe;CAC/D,MAAM,YAAY,gBAAgB;CAClC,MAAM,QAAQ,cAAc;CAC5B,MAAM,UAAU,qBAAqB;CAErC,MAAM,SAAS,MAAc,UAAyB;EACpD,IAAI,OACF,MAAM,SAAS,MAAM,KAAK;OAE1B,UAAU,MAAM,KAAK;EAEvB,SAAS,aAAa,QAAQ,MAAM,YAAY,IAAI,IAAI,IAAI;CAC9D;CACA,MAAM,aAAa,SAA0B,QAAQ,MAAM,SAAS,IAAI,IAAI;CAE5E,OAAO;EACL,OAAO,MAAM,OAAO;GAClB,MAAM,MAAM,KAAK;GACjB,IAAI,cACF,SAAS,UAAU,IAAI,CAAC;QAExB,YAAY,UAAU,IAAI,CAAC;EAE/B;EACA,OAAO,MAAM,OAAO;GAClB,MAAM,MAAM,KAAK;GACjB,YAAY,UAAU,IAAI,CAAC;EAC7B;EACA,KAAK,MAAM;GACT,IAAI,cACF,SAAS,UAAU,IAAI,CAAC;EAE5B;CACF;AACF"}
1
+ {"version":3,"file":"use-field-commit.js","names":[],"sources":["../../../resources/js/form/components/use-field-commit.ts"],"sourcesContent":["import { useFieldScope } from \"./field-scope\";\nimport { useFormContext } from \"./context\";\nimport { usePrefillController } from \"./prefill-context\";\nimport { useSetFormValue } from \"./values\";\n\ntype FieldCommit = {\n /** Write the value and validate immediately (precognitive) or clear errors. */\n commit: (name: string, value: unknown) => void;\n /** Write the value and clear errors, deferring validation to a later `blur`. */\n change: (name: string, value: unknown) => void;\n /** Validate the field now if precognitive (e.g. on blur or popover close). */\n blur: (name: string) => void;\n};\n\n/**\n * The shared field-mutation contract every form field uses to write its value\n * and drive precognition. Fields that validate on change call `commit`; fields\n * that validate on blur/close (rich editor, select) call `change` then `blur`.\n *\n * When called inside a `FieldScopeProvider`, writes go through the scope's\n * `setValue` and error paths use the scoped dot-key; outside a scope the\n * behavior is identical to before.\n */\nexport function useFieldCommit(): FieldCommit {\n const { clearErrors, precognitive, validate } = useFormContext();\n const setGlobal = useSetFormValue();\n const scope = useFieldScope();\n const prefill = usePrefillController();\n\n const write = (name: string, value: unknown): void => {\n if (scope) {\n scope.setValue(name, value);\n } else {\n setGlobal(name, value);\n }\n prefill?.markUserEdit(scope ? scope.overrideKey(name) : name);\n };\n const errorPath = (name: string): string => (scope ? scope.errorKey(name) : name);\n\n return {\n commit(name, value) {\n write(name, value);\n if (precognitive) {\n validate(errorPath(name));\n } else {\n clearErrors(errorPath(name));\n }\n },\n change(name, value) {\n write(name, value);\n clearErrors(errorPath(name));\n },\n blur(name) {\n if (precognitive) {\n validate(errorPath(name));\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAuBA,SAAgB,iBAA8B;CAC5C,MAAM,EAAE,aAAa,cAAc,aAAa,eAAe;CAC/D,MAAM,YAAY,gBAAgB;CAClC,MAAM,QAAQ,cAAc;CAC5B,MAAM,UAAU,qBAAqB;CAErC,MAAM,SAAS,MAAc,UAAyB;EACpD,IAAI,OACF,MAAM,SAAS,MAAM,KAAK;OAE1B,UAAU,MAAM,KAAK;EAEvB,SAAS,aAAa,QAAQ,MAAM,YAAY,IAAI,IAAI,IAAI;CAC9D;CACA,MAAM,aAAa,SAA0B,QAAQ,MAAM,SAAS,IAAI,IAAI;CAE5E,OAAO;EACL,OAAO,MAAM,OAAO;GAClB,MAAM,MAAM,KAAK;GACjB,IAAI,cACF,SAAS,UAAU,IAAI,CAAC;QAExB,YAAY,UAAU,IAAI,CAAC;EAE/B;EACA,OAAO,MAAM,OAAO;GAClB,MAAM,MAAM,KAAK;GACjB,YAAY,UAAU,IAAI,CAAC;EAC7B;EACA,KAAK,MAAM;GACT,IAAI,cACF,SAAS,UAAU,IAAI,CAAC;EAE5B;CACF;AACF"}
@@ -1,7 +1,8 @@
1
1
  import { Node } from '../../core/types';
2
2
  import { PrefillController } from './prefill-context';
3
- export type FormResolver = {
3
+ type FormResolver = {
4
4
  nodes: Record<string, Node>;
5
5
  markUserEdit: PrefillController["markUserEdit"];
6
6
  };
7
7
  export declare function useFormResolver(action: string, componentRef: string, nodes: Node[] | undefined): FormResolver;
8
+ export {};
@@ -1 +1 @@
1
- {"version":3,"file":"use-form-resolver.js","names":[],"sources":["../../../resources/js/form/components/use-form-resolver.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { walkFields } from \"./field-props\";\nimport { FORM_DEBOUNCE_MS, postFormAction } from \"./form-transport\";\nimport {\n collectPrefillTargets,\n getPath,\n pathsToClear,\n pruneOverrides,\n seededOverrides,\n} from \"./prefill-targets\";\nimport type { PrefillController } from \"./prefill-context\";\nimport { useFormValues, useSetFormValue } from \"./values\";\n\ntype ResolveResponse = {\n fields?: Record<string, Node>;\n values?: Record<string, unknown>;\n prefill?: Record<string, unknown>;\n};\n\nexport type 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 { _resolve: true, ...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/components/use-form-resolver.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { walkFields } from \"./field-props\";\nimport { FORM_DEBOUNCE_MS, postFormAction } from \"./form-transport\";\nimport {\n collectPrefillTargets,\n getPath,\n pathsToClear,\n pruneOverrides,\n seededOverrides,\n} from \"./prefill-targets\";\nimport type { PrefillController } from \"./prefill-context\";\nimport { useFormValues, useSetFormValue } from \"./values\";\n\ntype ResolveResponse = {\n fields?: Record<string, Node>;\n values?: Record<string, unknown>;\n prefill?: Record<string, unknown>;\n};\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 { _resolve: true, ...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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"}
@@ -0,0 +1,12 @@
1
+ export type FormatOptions = {
2
+ locale?: string;
3
+ timeZone?: string;
4
+ };
5
+ export type DateConfig = {
6
+ dateStyle: string | null;
7
+ timeStyle: string | null;
8
+ month?: string | null;
9
+ year?: string | null;
10
+ };
11
+ export declare function formatDateValue(value: unknown, date: DateConfig, options?: FormatOptions): string;
12
+ export declare function preciseDateTime(value: unknown, options?: FormatOptions): string;
@@ -0,0 +1,30 @@
1
+ //#region resources/js/format/date-time.ts
2
+ function formatDateValue(value, date, options) {
3
+ const parsed = new Date(String(value));
4
+ if (Number.isNaN(parsed.getTime())) return String(value ?? "");
5
+ const intl = { timeZone: options?.timeZone };
6
+ if (date.dateStyle) intl.dateStyle = date.dateStyle;
7
+ if (date.timeStyle) intl.timeStyle = date.timeStyle;
8
+ if (date.month) intl.month = date.month;
9
+ if (date.year) intl.year = date.year;
10
+ return new Intl.DateTimeFormat(options?.locale, intl).format(parsed);
11
+ }
12
+ function preciseDateTime(value, options) {
13
+ const date = new Date(String(value));
14
+ if (Number.isNaN(date.getTime())) return "";
15
+ const formatted = new Intl.DateTimeFormat(options?.locale, {
16
+ year: "numeric",
17
+ month: "short",
18
+ day: "numeric",
19
+ hour: "2-digit",
20
+ minute: "2-digit",
21
+ second: "2-digit",
22
+ timeZone: options?.timeZone,
23
+ timeZoneName: "short"
24
+ }).format(date);
25
+ return options?.timeZone ? `${formatted} (${options.timeZone})` : formatted;
26
+ }
27
+ //#endregion
28
+ export { formatDateValue, preciseDateTime };
29
+
30
+ //# sourceMappingURL=date-time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date-time.js","names":[],"sources":["../../resources/js/format/date-time.ts"],"sourcesContent":["export type FormatOptions = {\n locale?: string;\n timeZone?: string;\n};\n\nexport type DateConfig = {\n dateStyle: string | null;\n timeStyle: string | null;\n month?: string | null;\n year?: string | null;\n};\n\nexport function formatDateValue(value: unknown, date: DateConfig, options?: FormatOptions): string {\n const parsed = new Date(String(value));\n\n if (Number.isNaN(parsed.getTime())) {\n return String(value ?? \"\");\n }\n\n const intl: Intl.DateTimeFormatOptions = { timeZone: options?.timeZone };\n\n if (date.dateStyle) {\n intl.dateStyle = date.dateStyle as Intl.DateTimeFormatOptions[\"dateStyle\"];\n }\n\n if (date.timeStyle) {\n intl.timeStyle = date.timeStyle as Intl.DateTimeFormatOptions[\"timeStyle\"];\n }\n\n if (date.month) {\n intl.month = date.month as Intl.DateTimeFormatOptions[\"month\"];\n }\n\n if (date.year) {\n intl.year = date.year as Intl.DateTimeFormatOptions[\"year\"];\n }\n\n return new Intl.DateTimeFormat(options?.locale, intl).format(parsed);\n}\n\nexport function preciseDateTime(value: unknown, options?: FormatOptions): string {\n const date = new Date(String(value));\n\n if (Number.isNaN(date.getTime())) {\n return \"\";\n }\n\n const formatted = new Intl.DateTimeFormat(options?.locale, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n timeZone: options?.timeZone,\n timeZoneName: \"short\",\n }).format(date);\n\n return options?.timeZone ? `${formatted} (${options.timeZone})` : formatted;\n}\n"],"mappings":";AAYA,SAAgB,gBAAgB,OAAgB,MAAkB,SAAiC;CACjG,MAAM,SAAS,IAAI,KAAK,OAAO,KAAK,CAAC;CAErC,IAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,GAC/B,OAAO,OAAO,SAAS,EAAE;CAG3B,MAAM,OAAmC,EAAE,UAAU,SAAS,SAAS;CAEvE,IAAI,KAAK,WACP,KAAK,YAAY,KAAK;CAGxB,IAAI,KAAK,WACP,KAAK,YAAY,KAAK;CAGxB,IAAI,KAAK,OACP,KAAK,QAAQ,KAAK;CAGpB,IAAI,KAAK,MACP,KAAK,OAAO,KAAK;CAGnB,OAAO,IAAI,KAAK,eAAe,SAAS,QAAQ,IAAI,EAAE,OAAO,MAAM;AACrE;AAEA,SAAgB,gBAAgB,OAAgB,SAAiC;CAC/E,MAAM,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC;CAEnC,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAC7B,OAAO;CAGT,MAAM,YAAY,IAAI,KAAK,eAAe,SAAS,QAAQ;EACzD,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,UAAU,SAAS;EACnB,cAAc;CAChB,CAAC,EAAE,OAAO,IAAI;CAEd,OAAO,SAAS,WAAW,GAAG,UAAU,IAAI,QAAQ,SAAS,KAAK;AACpE"}
@@ -1,4 +1,4 @@
1
- import { formatDateValue } from "../table/format.js";
1
+ import { formatDateValue } from "./date-time.js";
2
2
  import { formatNumber } from "./number.js";
3
3
  //#region resources/js/format/value.ts
4
4
  function isDateFormat(format) {
@@ -1 +1 @@
1
- {"version":3,"file":"value.js","names":[],"sources":["../../resources/js/format/value.ts"],"sourcesContent":["import type { DateFormat, NumberFormat } from \"@lattice-php/lattice/types\";\nimport { formatDateValue } from \"../table/format\";\nimport { formatNumber } from \"./number\";\n\nexport type Format = NumberFormat | DateFormat;\n\nfunction isDateFormat(format: Format): format is DateFormat {\n return format.kind === \"date\";\n}\n\nexport function formatValue(\n value: unknown,\n format: Format | null,\n ctx: { locale: string; timezone: string },\n): string {\n if (format === null) {\n return String(value ?? \"\");\n }\n\n return isDateFormat(format)\n ? formatDateValue(value, format, { locale: ctx.locale, timeZone: ctx.timezone })\n : formatNumber(value, format, ctx.locale);\n}\n"],"mappings":";;;AAMA,SAAS,aAAa,QAAsC;CAC1D,OAAO,OAAO,SAAS;AACzB;AAEA,SAAgB,YACd,OACA,QACA,KACQ;CACR,IAAI,WAAW,MACb,OAAO,OAAO,SAAS,EAAE;CAG3B,OAAO,aAAa,MAAM,IACtB,gBAAgB,OAAO,QAAQ;EAAE,QAAQ,IAAI;EAAQ,UAAU,IAAI;CAAS,CAAC,IAC7E,aAAa,OAAO,QAAQ,IAAI,MAAM;AAC5C"}
1
+ {"version":3,"file":"value.js","names":[],"sources":["../../resources/js/format/value.ts"],"sourcesContent":["import type { DateFormat, NumberFormat } from \"@lattice-php/lattice/types\";\nimport { formatDateValue } from \"./date-time\";\nimport { formatNumber } from \"./number\";\n\nexport type Format = NumberFormat | DateFormat;\n\nfunction isDateFormat(format: Format): format is DateFormat {\n return format.kind === \"date\";\n}\n\nexport function formatValue(\n value: unknown,\n format: Format | null,\n ctx: { locale: string; timezone: string },\n): string {\n if (format === null) {\n return String(value ?? \"\");\n }\n\n return isDateFormat(format)\n ? formatDateValue(value, format, { locale: ctx.locale, timeZone: ctx.timezone })\n : formatNumber(value, format, ctx.locale);\n}\n"],"mappings":";;;AAMA,SAAS,aAAa,QAAsC;CAC1D,OAAO,OAAO,SAAS;AACzB;AAEA,SAAgB,YACd,OACA,QACA,KACQ;CACR,IAAI,WAAW,MACb,OAAO,OAAO,SAAS,EAAE;CAG3B,OAAO,aAAa,MAAM,IACtB,gBAAgB,OAAO,QAAQ;EAAE,QAAQ,IAAI;EAAQ,UAAU,IAAI;CAAS,CAAC,IAC7E,aAAa,OAAO,QAAQ,IAAI,MAAM;AAC5C"}
@@ -1,5 +1,5 @@
1
1
  import { I18nConfig } from '../types/generated';
2
- export type Config = {
2
+ type Config = {
3
3
  readonly locales: readonly string[];
4
4
  readonly timezone: string | null;
5
5
  };
@@ -7,3 +7,4 @@ export declare function setConfig(config: I18nConfig | undefined): void;
7
7
  export declare function configTimezone(): string | null;
8
8
  export declare function subscribeConfig(callback: () => void): () => void;
9
9
  export declare function useConfig(): Config;
10
+ export {};
@@ -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\nexport type 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,GAAG,KAAK,WAAW,OAAO,KAAK,CAAC,EAAE,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\";\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,GAAG,KAAK,WAAW,OAAO,KAAK,CAAC,EAAE,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,6 +1,6 @@
1
1
  import { useLocale } from "./locale.js";
2
2
  import { useTimezone } from "./timezone.js";
3
- import { formatDateValue, preciseDateTime } from "../table/format.js";
3
+ import { formatDateValue, preciseDateTime } from "../format/date-time.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  //#region resources/js/i18n/date-time.tsx
6
6
  function DateTime({ value, dateStyle = "medium", timeStyle = "short" }) {
@@ -1 +1 @@
1
- {"version":3,"file":"date-time.js","names":[],"sources":["../../resources/js/i18n/date-time.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { type FormatOptions, formatDateValue, preciseDateTime } from \"../table/format\";\nimport { useLocale } from \"./locale\";\nimport { useTimezone } from \"./timezone\";\n\nexport type DateTimeProps = {\n value: unknown;\n dateStyle?: string | null;\n timeStyle?: string | null;\n};\n\nexport function DateTime({\n value,\n dateStyle = \"medium\",\n timeStyle = \"short\",\n}: DateTimeProps): ReactNode {\n const { locale } = useLocale();\n const { timezone } = useTimezone();\n\n if (value === null || value === undefined || value === \"\") {\n return null;\n }\n\n const options: FormatOptions = { locale, timeZone: timezone };\n const text = formatDateValue(value, { dateStyle, timeStyle }, options);\n const iso = isoOrNull(value);\n const title = preciseDateTime(value, options);\n\n return (\n <time dateTime={iso ?? undefined} title={title || undefined}>\n {text}\n </time>\n );\n}\n\nfunction isoOrNull(value: unknown): string | null {\n const date = new Date(String(value));\n\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n"],"mappings":";;;;;AAWA,SAAgB,SAAS,EACvB,OACA,YAAY,UACZ,YAAY,WACe;CAC3B,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,EAAE,aAAa,YAAY;CAEjC,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,IACrD,OAAO;CAGT,MAAM,UAAyB;EAAE;EAAQ,UAAU;CAAS;CAC5D,MAAM,OAAO,gBAAgB,OAAO;EAAE;EAAW;CAAU,GAAG,OAAO;CACrE,MAAM,MAAM,UAAU,KAAK;CAC3B,MAAM,QAAQ,gBAAgB,OAAO,OAAO;CAE5C,OACE,oBAAC,QAAD;EAAM,UAAU,OAAO,KAAA;EAAW,OAAO,SAAS,KAAA;YAC/C;CACG,CAAA;AAEV;AAEA,SAAS,UAAU,OAA+B;CAChD,MAAM,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC;CAEnC,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,OAAO,KAAK,YAAY;AAChE"}
1
+ {"version":3,"file":"date-time.js","names":[],"sources":["../../resources/js/i18n/date-time.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport {\n type FormatOptions,\n formatDateValue,\n preciseDateTime,\n} from \"@lattice-php/lattice/format/date-time\";\nimport { useLocale } from \"./locale\";\nimport { useTimezone } from \"./timezone\";\n\nexport type DateTimeProps = {\n value: unknown;\n dateStyle?: string | null;\n timeStyle?: string | null;\n};\n\nexport function DateTime({\n value,\n dateStyle = \"medium\",\n timeStyle = \"short\",\n}: DateTimeProps): ReactNode {\n const { locale } = useLocale();\n const { timezone } = useTimezone();\n\n if (value === null || value === undefined || value === \"\") {\n return null;\n }\n\n const options: FormatOptions = { locale, timeZone: timezone };\n const text = formatDateValue(value, { dateStyle, timeStyle }, options);\n const iso = isoOrNull(value);\n const title = preciseDateTime(value, options);\n\n return (\n <time dateTime={iso ?? undefined} title={title || undefined}>\n {text}\n </time>\n );\n}\n\nfunction isoOrNull(value: unknown): string | null {\n const date = new Date(String(value));\n\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n"],"mappings":";;;;;AAeA,SAAgB,SAAS,EACvB,OACA,YAAY,UACZ,YAAY,WACe;CAC3B,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,EAAE,aAAa,YAAY;CAEjC,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,IACrD,OAAO;CAGT,MAAM,UAAyB;EAAE;EAAQ,UAAU;CAAS;CAC5D,MAAM,OAAO,gBAAgB,OAAO;EAAE;EAAW;CAAU,GAAG,OAAO;CACrE,MAAM,MAAM,UAAU,KAAK;CAC3B,MAAM,QAAQ,gBAAgB,OAAO,OAAO;CAE5C,OACE,oBAAC,QAAD;EAAM,UAAU,OAAO,KAAA;EAAW,OAAO,SAAS,KAAA;YAC/C;CACG,CAAA;AAEV;AAEA,SAAS,UAAU,OAA+B;CAChD,MAAM,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC;CAEnC,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,OAAO,KAAK,YAAY;AAChE"}
@@ -1,6 +1,6 @@
1
1
  import { i18n as I18nInstance, InitOptions } from 'i18next';
2
2
  type TranslationFunction = (key: string, defaultValue?: string, options?: Record<string, unknown>) => string;
3
- export type TranslationResult = {
3
+ type TranslationResult = {
4
4
  t: TranslationFunction;
5
5
  i18n: I18nInstance;
6
6
  locale: string;
@@ -1 +1 @@
1
- {"version":3,"file":"instance.js","names":[],"sources":["../../resources/js/i18n/instance.ts"],"sourcesContent":["import i18next, { type i18n as I18nInstance, type InitOptions } from \"i18next\";\nimport { useCallback, useSyncExternalStore } from \"react\";\nimport { useConfig } from \"./config\";\nimport { currentLocale, subscribeLocale, useLocale } from \"./locale\";\n\nconst NAMESPACE = \"lattice\";\n\ntype TranslationFunction = (\n key: string,\n defaultValue?: string,\n options?: Record<string, unknown>,\n) => string;\n\nexport type TranslationResult = {\n t: TranslationFunction;\n i18n: I18nInstance;\n locale: string;\n locales: readonly string[];\n ready: boolean;\n setLocale: (locale: string) => void;\n};\n\nexport const i18n: I18nInstance = i18next.createInstance();\n\nlet initialization: Promise<unknown> | undefined;\nlet revision = 0;\n\nfunction subscribe(onStoreChange: () => void): () => void {\n const listener = () => {\n revision += 1;\n onStoreChange();\n };\n\n i18n.on(\"initialized\", listener);\n i18n.on(\"loaded\", listener);\n i18n.on(\"languageChanged\", listener);\n i18n.store?.on(\"added\", listener);\n i18n.store?.on(\"removed\", listener);\n\n return () => {\n i18n.off(\"initialized\", listener);\n i18n.off(\"loaded\", listener);\n i18n.off(\"languageChanged\", listener);\n i18n.store?.off(\"added\", listener);\n i18n.store?.off(\"removed\", listener);\n };\n}\n\nfunction snapshot(): number {\n return revision;\n}\n\n/**\n * Initialize the instance exactly once. The first caller wins — a rendered\n * component (inline English, zero config) or `enableBackend()`, which registers\n * its backend first because i18next can only wire a backend during `init`.\n */\nexport function ensureI18n(extend?: (base: InitOptions) => InitOptions): Promise<unknown> {\n if (!initialization) {\n const base: InitOptions = {\n lng: currentLocale(),\n fallbackLng: \"en\",\n ns: [NAMESPACE],\n defaultNS: NAMESPACE,\n interpolation: { escapeValue: false },\n };\n\n initialization = i18n.init(extend ? extend(base) : base);\n }\n\n return initialization;\n}\n\nsubscribeLocale((locale) => {\n void ensureI18n().then(() => {\n if (i18n.language !== locale) {\n void i18n.changeLanguage(locale);\n }\n });\n});\n\n/**\n * Eagerly load the namespaces for the given locales so a later\n * `changeLanguage` resolves them from the store instead of an HTTP round-trip,\n * which would otherwise flash the fallback language on switch. The active\n * locale is already loaded by {@link ensureI18n}, so it is skipped.\n */\nexport async function preloadLanguages(locales: readonly string[]): Promise<void> {\n const pending = locales.filter((locale) => locale !== currentLocale());\n\n if (pending.length === 0) {\n return;\n }\n\n await ensureI18n();\n await i18n.loadLanguages([...pending]);\n}\n\nexport function useT(namespace: string): TranslationResult {\n ensureI18n();\n useSyncExternalStore(subscribe, snapshot, snapshot);\n const { locales } = useConfig();\n const { locale, setLocale } = useLocale();\n\n const t = useCallback<TranslationFunction>(\n (key, defaultValue = key, options = {}) => translate(namespace, key, defaultValue, options),\n [namespace],\n );\n\n return { t, i18n, locale, locales, ready: i18n.isInitialized, setLocale };\n}\n\nexport function translate(\n namespace: string,\n key: string,\n defaultValue: string,\n options?: Record<string, unknown>,\n): string {\n ensureI18n();\n\n if (!i18n.isInitialized || !i18n.hasLoadedNamespace(namespace)) {\n return i18n.t(key, defaultValue, { ns: namespace, ...options, saveMissing: false });\n }\n\n return i18n.t(key, defaultValue, { ns: namespace, ...options });\n}\n"],"mappings":";;;;;AAKA,IAAM,YAAY;AAiBlB,IAAa,OAAqB,QAAQ,eAAe;AAEzD,IAAI;AACJ,IAAI,WAAW;AAEf,SAAS,UAAU,eAAuC;CACxD,MAAM,iBAAiB;EACrB,YAAY;EACZ,cAAc;CAChB;CAEA,KAAK,GAAG,eAAe,QAAQ;CAC/B,KAAK,GAAG,UAAU,QAAQ;CAC1B,KAAK,GAAG,mBAAmB,QAAQ;CACnC,KAAK,OAAO,GAAG,SAAS,QAAQ;CAChC,KAAK,OAAO,GAAG,WAAW,QAAQ;CAElC,aAAa;EACX,KAAK,IAAI,eAAe,QAAQ;EAChC,KAAK,IAAI,UAAU,QAAQ;EAC3B,KAAK,IAAI,mBAAmB,QAAQ;EACpC,KAAK,OAAO,IAAI,SAAS,QAAQ;EACjC,KAAK,OAAO,IAAI,WAAW,QAAQ;CACrC;AACF;AAEA,SAAS,WAAmB;CAC1B,OAAO;AACT;;;;;;AAOA,SAAgB,WAAW,QAA+D;CACxF,IAAI,CAAC,gBAAgB;EACnB,MAAM,OAAoB;GACxB,KAAK,cAAc;GACnB,aAAa;GACb,IAAI,CAAC,SAAS;GACd,WAAW;GACX,eAAe,EAAE,aAAa,MAAM;EACtC;EAEA,iBAAiB,KAAK,KAAK,SAAS,OAAO,IAAI,IAAI,IAAI;CACzD;CAEA,OAAO;AACT;AAEA,iBAAiB,WAAW;CAC1B,WAAgB,EAAE,WAAW;EAC3B,IAAI,KAAK,aAAa,QACpB,KAAU,eAAe,MAAM;CAEnC,CAAC;AACH,CAAC;;;;;;;AAQD,eAAsB,iBAAiB,SAA2C;CAChF,MAAM,UAAU,QAAQ,QAAQ,WAAW,WAAW,cAAc,CAAC;CAErE,IAAI,QAAQ,WAAW,GACrB;CAGF,MAAM,WAAW;CACjB,MAAM,KAAK,cAAc,CAAC,GAAG,OAAO,CAAC;AACvC;AAEA,SAAgB,KAAK,WAAsC;CACzD,WAAW;CACX,qBAAqB,WAAW,UAAU,QAAQ;CAClD,MAAM,EAAE,YAAY,UAAU;CAC9B,MAAM,EAAE,QAAQ,cAAc,UAAU;CAOxC,OAAO;EAAE,GALC,aACP,KAAK,eAAe,KAAK,UAAU,CAAC,MAAM,UAAU,WAAW,KAAK,cAAc,OAAO,GAC1F,CAAC,SAAS,CAGH;EAAG;EAAM;EAAQ;EAAS,OAAO,KAAK;EAAe;CAAU;AAC1E;AAEA,SAAgB,UACd,WACA,KACA,cACA,SACQ;CACR,WAAW;CAEX,IAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,mBAAmB,SAAS,GAC3D,OAAO,KAAK,EAAE,KAAK,cAAc;EAAE,IAAI;EAAW,GAAG;EAAS,aAAa;CAAM,CAAC;CAGpF,OAAO,KAAK,EAAE,KAAK,cAAc;EAAE,IAAI;EAAW,GAAG;CAAQ,CAAC;AAChE"}
1
+ {"version":3,"file":"instance.js","names":[],"sources":["../../resources/js/i18n/instance.ts"],"sourcesContent":["import i18next, { type i18n as I18nInstance, type InitOptions } from \"i18next\";\nimport { useCallback, useSyncExternalStore } from \"react\";\nimport { useConfig } from \"./config\";\nimport { currentLocale, subscribeLocale, useLocale } from \"./locale\";\n\nconst NAMESPACE = \"lattice\";\n\ntype TranslationFunction = (\n key: string,\n defaultValue?: string,\n options?: Record<string, unknown>,\n) => string;\n\ntype TranslationResult = {\n t: TranslationFunction;\n i18n: I18nInstance;\n locale: string;\n locales: readonly string[];\n ready: boolean;\n setLocale: (locale: string) => void;\n};\n\nexport const i18n: I18nInstance = i18next.createInstance();\n\nlet initialization: Promise<unknown> | undefined;\nlet revision = 0;\n\nfunction subscribe(onStoreChange: () => void): () => void {\n const listener = () => {\n revision += 1;\n onStoreChange();\n };\n\n i18n.on(\"initialized\", listener);\n i18n.on(\"loaded\", listener);\n i18n.on(\"languageChanged\", listener);\n i18n.store?.on(\"added\", listener);\n i18n.store?.on(\"removed\", listener);\n\n return () => {\n i18n.off(\"initialized\", listener);\n i18n.off(\"loaded\", listener);\n i18n.off(\"languageChanged\", listener);\n i18n.store?.off(\"added\", listener);\n i18n.store?.off(\"removed\", listener);\n };\n}\n\nfunction snapshot(): number {\n return revision;\n}\n\n/**\n * Initialize the instance exactly once. The first caller wins — a rendered\n * component (inline English, zero config) or `enableBackend()`, which registers\n * its backend first because i18next can only wire a backend during `init`.\n */\nexport function ensureI18n(extend?: (base: InitOptions) => InitOptions): Promise<unknown> {\n if (!initialization) {\n const base: InitOptions = {\n lng: currentLocale(),\n fallbackLng: \"en\",\n ns: [NAMESPACE],\n defaultNS: NAMESPACE,\n interpolation: { escapeValue: false },\n };\n\n initialization = i18n.init(extend ? extend(base) : base);\n }\n\n return initialization;\n}\n\nsubscribeLocale((locale) => {\n void ensureI18n().then(() => {\n if (i18n.language !== locale) {\n void i18n.changeLanguage(locale);\n }\n });\n});\n\n/**\n * Eagerly load the namespaces for the given locales so a later\n * `changeLanguage` resolves them from the store instead of an HTTP round-trip,\n * which would otherwise flash the fallback language on switch. The active\n * locale is already loaded by {@link ensureI18n}, so it is skipped.\n */\nexport async function preloadLanguages(locales: readonly string[]): Promise<void> {\n const pending = locales.filter((locale) => locale !== currentLocale());\n\n if (pending.length === 0) {\n return;\n }\n\n await ensureI18n();\n await i18n.loadLanguages([...pending]);\n}\n\nexport function useT(namespace: string): TranslationResult {\n ensureI18n();\n useSyncExternalStore(subscribe, snapshot, snapshot);\n const { locales } = useConfig();\n const { locale, setLocale } = useLocale();\n\n const t = useCallback<TranslationFunction>(\n (key, defaultValue = key, options = {}) => translate(namespace, key, defaultValue, options),\n [namespace],\n );\n\n return { t, i18n, locale, locales, ready: i18n.isInitialized, setLocale };\n}\n\nexport function translate(\n namespace: string,\n key: string,\n defaultValue: string,\n options?: Record<string, unknown>,\n): string {\n ensureI18n();\n\n if (!i18n.isInitialized || !i18n.hasLoadedNamespace(namespace)) {\n return i18n.t(key, defaultValue, { ns: namespace, ...options, saveMissing: false });\n }\n\n return i18n.t(key, defaultValue, { ns: namespace, ...options });\n}\n"],"mappings":";;;;;AAKA,IAAM,YAAY;AAiBlB,IAAa,OAAqB,QAAQ,eAAe;AAEzD,IAAI;AACJ,IAAI,WAAW;AAEf,SAAS,UAAU,eAAuC;CACxD,MAAM,iBAAiB;EACrB,YAAY;EACZ,cAAc;CAChB;CAEA,KAAK,GAAG,eAAe,QAAQ;CAC/B,KAAK,GAAG,UAAU,QAAQ;CAC1B,KAAK,GAAG,mBAAmB,QAAQ;CACnC,KAAK,OAAO,GAAG,SAAS,QAAQ;CAChC,KAAK,OAAO,GAAG,WAAW,QAAQ;CAElC,aAAa;EACX,KAAK,IAAI,eAAe,QAAQ;EAChC,KAAK,IAAI,UAAU,QAAQ;EAC3B,KAAK,IAAI,mBAAmB,QAAQ;EACpC,KAAK,OAAO,IAAI,SAAS,QAAQ;EACjC,KAAK,OAAO,IAAI,WAAW,QAAQ;CACrC;AACF;AAEA,SAAS,WAAmB;CAC1B,OAAO;AACT;;;;;;AAOA,SAAgB,WAAW,QAA+D;CACxF,IAAI,CAAC,gBAAgB;EACnB,MAAM,OAAoB;GACxB,KAAK,cAAc;GACnB,aAAa;GACb,IAAI,CAAC,SAAS;GACd,WAAW;GACX,eAAe,EAAE,aAAa,MAAM;EACtC;EAEA,iBAAiB,KAAK,KAAK,SAAS,OAAO,IAAI,IAAI,IAAI;CACzD;CAEA,OAAO;AACT;AAEA,iBAAiB,WAAW;CAC1B,WAAgB,EAAE,WAAW;EAC3B,IAAI,KAAK,aAAa,QACpB,KAAU,eAAe,MAAM;CAEnC,CAAC;AACH,CAAC;;;;;;;AAQD,eAAsB,iBAAiB,SAA2C;CAChF,MAAM,UAAU,QAAQ,QAAQ,WAAW,WAAW,cAAc,CAAC;CAErE,IAAI,QAAQ,WAAW,GACrB;CAGF,MAAM,WAAW;CACjB,MAAM,KAAK,cAAc,CAAC,GAAG,OAAO,CAAC;AACvC;AAEA,SAAgB,KAAK,WAAsC;CACzD,WAAW;CACX,qBAAqB,WAAW,UAAU,QAAQ;CAClD,MAAM,EAAE,YAAY,UAAU;CAC9B,MAAM,EAAE,QAAQ,cAAc,UAAU;CAOxC,OAAO;EAAE,GALC,aACP,KAAK,eAAe,KAAK,UAAU,CAAC,MAAM,UAAU,WAAW,KAAK,cAAc,OAAO,GAC1F,CAAC,SAAS,CAGH;EAAG;EAAM;EAAQ;EAAS,OAAO,KAAK;EAAe;CAAU;AAC1E;AAEA,SAAgB,UACd,WACA,KACA,cACA,SACQ;CACR,WAAW;CAEX,IAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,mBAAmB,SAAS,GAC3D,OAAO,KAAK,EAAE,KAAK,cAAc;EAAE,IAAI;EAAW,GAAG;EAAS,aAAa;CAAM,CAAC;CAGpF,OAAO,KAAK,EAAE,KAAK,cAAc;EAAE,IAAI;EAAW,GAAG;CAAQ,CAAC;AAChE"}
@@ -1,3 +1,4 @@
1
1
  import { VisitOptions } from '@inertiajs/core';
2
- export type LocaleReloadProps = Pick<VisitOptions, "preserveScroll" | "preserveState">;
2
+ type LocaleReloadProps = Pick<VisitOptions, "preserveScroll" | "preserveState">;
3
3
  export declare function LocaleReload({ preserveScroll, preserveState }: LocaleReloadProps): import("react").JSX.Element;
4
+ export {};
@@ -1 +1 @@
1
- {"version":3,"file":"locale-reload.js","names":[],"sources":["../../resources/js/i18n/locale-reload.tsx"],"sourcesContent":["import type { VisitOptions } from \"@inertiajs/core\";\nimport { router } from \"@inertiajs/react\";\nimport { useCallback } from \"react\";\nimport { EventBridge } from \"../events/event-bridge\";\n\nexport type LocaleReloadProps = Pick<VisitOptions, \"preserveScroll\" | \"preserveState\">;\n\n// preserveState by default: the visit only re-fetches the re-localized props,\n// so keeping the page mounted avoids remounting the whole tree (and losing\n// table sort/filter, form input, focus) on every locale switch.\nexport function LocaleReload({ preserveScroll = true, preserveState = true }: LocaleReloadProps) {\n const reload = useCallback(() => {\n router.visit(window.location.href, { preserveScroll, preserveState });\n }, [preserveScroll, preserveState]);\n\n return <EventBridge onLocaleChange={reload} />;\n}\n"],"mappings":";;;;;AAUA,SAAgB,aAAa,EAAE,iBAAiB,MAAM,gBAAgB,QAA2B;CAK/F,OAAO,oBAAC,aAAD,EAAa,gBAJL,kBAAkB;EAC/B,OAAO,MAAM,OAAO,SAAS,MAAM;GAAE;GAAgB;EAAc,CAAC;CACtE,GAAG,CAAC,gBAAgB,aAAa,CAEG,EAAS,CAAA;AAC/C"}
1
+ {"version":3,"file":"locale-reload.js","names":[],"sources":["../../resources/js/i18n/locale-reload.tsx"],"sourcesContent":["import type { VisitOptions } from \"@inertiajs/core\";\nimport { router } from \"@inertiajs/react\";\nimport { useCallback } from \"react\";\nimport { EventBridge } from \"../events/event-bridge\";\n\ntype LocaleReloadProps = Pick<VisitOptions, \"preserveScroll\" | \"preserveState\">;\n\n// preserveState by default: the visit only re-fetches the re-localized props,\n// so keeping the page mounted avoids remounting the whole tree (and losing\n// table sort/filter, form input, focus) on every locale switch.\nexport function LocaleReload({ preserveScroll = true, preserveState = true }: LocaleReloadProps) {\n const reload = useCallback(() => {\n router.visit(window.location.href, { preserveScroll, preserveState });\n }, [preserveScroll, preserveState]);\n\n return <EventBridge onLocaleChange={reload} />;\n}\n"],"mappings":";;;;;AAUA,SAAgB,aAAa,EAAE,iBAAiB,MAAM,gBAAgB,QAA2B;CAK/F,OAAO,oBAAC,aAAD,EAAa,gBAJL,kBAAkB;EAC/B,OAAO,MAAM,OAAO,SAAS,MAAM;GAAE;GAAgB;EAAc,CAAC;CACtE,GAAG,CAAC,gBAAgB,aAAa,CAEG,EAAS,CAAA;AAC/C"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { initializeTheme, updateAppearance, useAppearance } from "./appearance/index.js";
2
+ import { createPlugin, createRegistry, eagerComponent, extendRegistry, lazyComponent } from "./core/registry.js";
2
3
  import { useColumnRegistry, useComponentRegistry } from "./core/registry-context.js";
3
4
  import { LATTICE_EVENT } from "./events/event-names.js";
4
5
  import { LATTICE_REF_HEADER, withRefHeader } from "./core/component-ref.js";
@@ -11,7 +12,6 @@ import { onToast, showToast } from "./toast/toast.js";
11
12
  import { onCallout } from "./toast/callout.js";
12
13
  import { EventBridge } from "./events/event-bridge.js";
13
14
  import Callouts from "./layout/callouts.js";
14
- import { createPlugin, createRegistry, eagerComponent, extendRegistry, lazyComponent } from "./core/registry.js";
15
15
  import { builtinEffectHandlers, effectHandler, mergeEffectHandlers } from "./effects/registry.js";
16
16
  import { dispatchActionError, dispatchEffects, getActionEffects, isActionEffect } from "./effects/dispatch.js";
17
17
  import { useEffectDispatcher } from "./effects/use-effect-dispatcher.js";
@@ -1,5 +1,5 @@
1
- import Callouts from "./callouts.js";
2
1
  import { createPlugin, eagerComponent } from "../core/registry.js";
2
+ import Callouts from "./callouts.js";
3
3
  import BreadcrumbsComponent from "./breadcrumbs.js";
4
4
  import DropdownComponent from "./dropdown.js";
5
5
  import MenuComponent from "./menu.js";
@@ -4,7 +4,7 @@ import { IconRenderer } from "../icons/icon-renderer.js";
4
4
  import { useCollapsed } from "../core/collapsed-context.js";
5
5
  import { prefixedNodeTestId } from "../core/test-id.js";
6
6
  import { Popover } from "./popover.js";
7
- import { useAction } from "../action/use-action.js";
7
+ import { ActionTrigger, useClickBehavior } from "../core/use-click-behavior.js";
8
8
  import { Link, usePage } from "@inertiajs/react";
9
9
  import { useState } from "react";
10
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -25,15 +25,12 @@ function schemaContainsPath(schema, path) {
25
25
  }
26
26
  var MenuItemComponent = ({ children, node }) => {
27
27
  const collapsed = useCollapsed();
28
- const icon = node.props.icon;
29
- const label = node.props.label;
30
- const prefix = node.props.prefix;
31
- const suffix = node.props.suffix;
28
+ const { icon, label, prefix, suffix } = node.props;
32
29
  const iconOnly = Boolean(icon);
33
30
  const flyoutIcon = icon ?? prefix?.icon ?? null;
34
- const href = node.props.href ?? "";
35
31
  const currentPath = usePage().url.split("?")[0];
36
32
  const testId = prefixedNodeTestId("menu", node);
33
+ const behavior = useClickBehavior(node.props);
37
34
  const content = icon ? /* @__PURE__ */ jsx(IconRenderer, {
38
35
  className: "size-lt-icon-md shrink-0",
39
36
  icon
@@ -49,44 +46,53 @@ var MenuItemComponent = ({ children, node }) => {
49
46
  className: "ml-auto"
50
47
  }) : null
51
48
  ] });
52
- const action = node.props.action;
53
- if (action && href === "" && !children) return /* @__PURE__ */ jsx(ActionMenuItem, {
54
- action,
55
- className: cn(rowClass, "w-full", collapsed && "group relative justify-center", iconOnly && "justify-center"),
56
- label: collapsed || iconOnly ? label : void 0,
57
- testId,
49
+ const rowClassName = cn(rowClass, "w-full", collapsed && "group relative justify-center", iconOnly && "justify-center");
50
+ const triggerButton = ({ onClick, processing }) => /* @__PURE__ */ jsx("button", {
51
+ "aria-label": collapsed || iconOnly ? label : void 0,
52
+ className: rowClassName,
53
+ "data-test": testId,
54
+ disabled: processing,
55
+ onClick,
56
+ type: "button",
58
57
  children: content
59
58
  });
60
- if (href === "") {
61
- if (!children) return collapsed ? null : /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("span", {
62
- className: "flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase",
59
+ if (behavior.kind === "action") return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(ActionTrigger, {
60
+ action: behavior.action,
61
+ children: triggerButton
62
+ }) });
63
+ if (behavior.kind === "effects") return /* @__PURE__ */ jsx("li", { children: triggerButton({
64
+ onClick: behavior.onClick,
65
+ processing: false
66
+ }) });
67
+ if (behavior.kind === "navigate") {
68
+ const active = currentPath === behavior.href;
69
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, {
70
+ "aria-current": active ? "page" : void 0,
71
+ "aria-label": collapsed || iconOnly ? label : void 0,
72
+ as: behavior.method === "get" ? void 0 : "button",
73
+ className: cn(rowClassName, active && "bg-lt-muted font-medium"),
74
+ "data-test": testId,
75
+ href: behavior.href,
76
+ method: behavior.method,
63
77
  children: content
64
78
  }) });
65
- if (collapsed) return /* @__PURE__ */ jsx(FlyoutGroup, {
66
- icon: flyoutIcon,
67
- label,
68
- testId,
69
- children
70
- });
71
- return /* @__PURE__ */ jsx(CollapsibleItem, {
72
- content,
73
- defaultOpen: schemaContainsPath(node.schema, currentPath),
74
- testId,
75
- children
76
- });
77
79
  }
78
- const active = currentPath === href;
79
- const method = node.props.method ?? "get";
80
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link, {
81
- "aria-current": active ? "page" : void 0,
82
- "aria-label": collapsed || iconOnly ? label : void 0,
83
- as: method === "get" ? void 0 : "button",
84
- className: cn(rowClass, "w-full", collapsed && "group relative justify-center", iconOnly && "justify-center", active && "bg-lt-muted font-medium"),
85
- "data-test": testId,
86
- href,
87
- method,
80
+ if (!children) return collapsed ? null : /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("span", {
81
+ className: "flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase",
88
82
  children: content
89
83
  }) });
84
+ if (collapsed) return /* @__PURE__ */ jsx(FlyoutGroup, {
85
+ icon: flyoutIcon,
86
+ label,
87
+ testId,
88
+ children
89
+ });
90
+ return /* @__PURE__ */ jsx(CollapsibleItem, {
91
+ content,
92
+ defaultOpen: schemaContainsPath(node.schema, currentPath),
93
+ testId,
94
+ children
95
+ });
90
96
  };
91
97
  function CollapsibleItem({ children, content, defaultOpen, testId }) {
92
98
  const [open, setOpen] = useState(defaultOpen);
@@ -125,18 +131,6 @@ function FlyoutGroup({ children, icon, label, testId }) {
125
131
  })
126
132
  }) });
127
133
  }
128
- function ActionMenuItem({ action, children, className, label, testId }) {
129
- const { processing, requestSubmit, overlays } = useAction(action);
130
- return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("button", {
131
- "aria-label": label,
132
- className,
133
- "data-test": testId,
134
- disabled: processing,
135
- onClick: requestSubmit,
136
- type: "button",
137
- children
138
- }), overlays] });
139
- }
140
134
  //#endregion
141
135
  export { MenuItemComponent as default };
142
136
 
@@ -1 +1 @@
1
- {"version":3,"file":"menu-item.js","names":[],"sources":["../../resources/js/layout/menu-item.tsx"],"sourcesContent":["import { Link, usePage } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useAction } from \"@lattice-php/lattice/action/use-action\";\nimport { prefixedNodeTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { Node, RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport { Icon, IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { Affix } from \"@lattice-php/lattice/types/generated\";\nimport { useSidebarCollapsed } from \"./context\";\nimport { Popover } from \"./popover\";\n\nconst rowClass =\n \"flex items-center gap-2 rounded-lt-sm px-3 py-2 text-base font-medium text-lt-fg transition-colors hover:bg-lt-muted\";\n\nfunction MenuAffix({ affix, className }: { affix: Affix; className?: string }) {\n if (affix.icon) {\n return <IconRenderer className={cn(\"size-lt-icon-md shrink-0\", className)} icon={affix.icon} />;\n }\n\n return <span className={cn(\"shrink-0 text-sm text-lt-muted-fg\", className)}>{affix.text}</span>;\n}\n\nfunction schemaContainsPath(schema: Schema | undefined, path: string): boolean {\n return (schema ?? []).some(\n (child) => child.props?.href === path || schemaContainsPath(child.schema, path),\n );\n}\n\nconst MenuItemComponent: RendererComponent<\"menu-item\"> = ({ children, node }) => {\n const collapsed = useSidebarCollapsed();\n const icon = node.props.icon;\n const label = node.props.label;\n const prefix = node.props.prefix;\n const suffix = node.props.suffix;\n const iconOnly = Boolean(icon);\n const flyoutIcon = icon ?? prefix?.icon ?? null;\n const href = node.props.href ?? \"\";\n const currentPath = usePage().url.split(\"?\")[0];\n const testId = prefixedNodeTestId(\"menu\", node);\n\n const content = icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <>\n {prefix ? <MenuAffix affix={prefix} /> : null}\n {collapsed ? (\n <span\n className=\"pointer-events-none absolute top-1/2 left-full z-lt-popover ml-2 hidden -translate-y-1/2 rounded-lt-sm border border-lt-border bg-lt-popover px-2 py-1 text-sm whitespace-nowrap text-lt-popover-fg shadow-lt-md group-hover:block\"\n role=\"tooltip\"\n >\n {label}\n </span>\n ) : (\n <span>{label}</span>\n )}\n {suffix ? <MenuAffix affix={suffix} className=\"ml-auto\" /> : null}\n </>\n );\n\n const action = node.props.action;\n\n if (action && href === \"\" && !children) {\n return (\n <ActionMenuItem\n action={action as Node<\"action\">}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n )}\n label={collapsed || iconOnly ? label : undefined}\n testId={testId}\n >\n {content}\n </ActionMenuItem>\n );\n }\n\n if (href === \"\") {\n if (!children) {\n return collapsed ? null : (\n <li>\n <span className=\"flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {content}\n </span>\n </li>\n );\n }\n\n if (collapsed) {\n return (\n <FlyoutGroup icon={flyoutIcon} label={label} testId={testId}>\n {children}\n </FlyoutGroup>\n );\n }\n\n return (\n <CollapsibleItem\n content={content}\n defaultOpen={schemaContainsPath(node.schema, currentPath)}\n testId={testId}\n >\n {children}\n </CollapsibleItem>\n );\n }\n\n const active = currentPath === href;\n const method = node.props.method ?? \"get\";\n\n return (\n <li>\n <Link\n aria-current={active ? \"page\" : undefined}\n aria-label={collapsed || iconOnly ? label : undefined}\n as={method === \"get\" ? undefined : \"button\"}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n active && \"bg-lt-muted font-medium\",\n )}\n data-test={testId}\n href={href}\n method={method}\n >\n {content}\n </Link>\n </li>\n );\n};\n\nfunction CollapsibleItem({\n children,\n content,\n defaultOpen,\n testId,\n}: {\n children: ReactNode;\n content: ReactNode;\n defaultOpen: boolean;\n testId?: string;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n return (\n <li>\n <button\n aria-expanded={open}\n className={cn(rowClass, \"w-full\")}\n data-test={testId}\n onClick={() => setOpen((value) => !value)}\n type=\"button\"\n >\n {content}\n <Icon\n name=\"chevron-right\"\n className={cn(\n \"ml-auto size-lt-icon-md shrink-0 transition-transform\",\n open && \"rotate-90\",\n )}\n />\n </button>\n {open ? <ul className=\"mt-1 flex flex-col gap-1 pl-3\">{children}</ul> : null}\n </li>\n );\n}\n\nfunction FlyoutGroup({\n children,\n icon,\n label,\n testId,\n}: {\n children: ReactNode;\n icon?: string | null;\n label: string;\n testId?: string;\n}) {\n return (\n <li>\n <Popover\n className=\"min-w-48\"\n placement=\"right\"\n testId={testId}\n trigger={\n icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <span>{label}</span>\n )\n }\n triggerClassName={cn(rowClass, \"justify-center\")}\n triggerLabel={label}\n >\n <ul className=\"flex flex-col gap-1\">\n <li className=\"px-3 py-1.5 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {label}\n </li>\n {children}\n </ul>\n </Popover>\n </li>\n );\n}\n\nfunction ActionMenuItem({\n action,\n children,\n className,\n label,\n testId,\n}: {\n action: Node<\"action\">;\n children: ReactNode;\n className: string;\n label?: string;\n testId?: string;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <li>\n <button\n aria-label={label}\n className={className}\n data-test={testId}\n disabled={processing}\n onClick={requestSubmit}\n type=\"button\"\n >\n {children}\n </button>\n {overlays}\n </li>\n );\n}\n\nexport default MenuItemComponent;\n"],"mappings":"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"}
1
+ {"version":3,"file":"menu-item.js","names":[],"sources":["../../resources/js/layout/menu-item.tsx"],"sourcesContent":["import { Link, usePage } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { prefixedNodeTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport {\n ActionTrigger,\n type TriggerState,\n useClickBehavior,\n} from \"@lattice-php/lattice/core/use-click-behavior\";\nimport { Icon, IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { Affix } from \"@lattice-php/lattice/types/generated\";\nimport { useSidebarCollapsed } from \"./context\";\nimport { Popover } from \"./popover\";\n\nconst rowClass =\n \"flex items-center gap-2 rounded-lt-sm px-3 py-2 text-base font-medium text-lt-fg transition-colors hover:bg-lt-muted\";\n\nfunction MenuAffix({ affix, className }: { affix: Affix; className?: string }) {\n if (affix.icon) {\n return <IconRenderer className={cn(\"size-lt-icon-md shrink-0\", className)} icon={affix.icon} />;\n }\n\n return <span className={cn(\"shrink-0 text-sm text-lt-muted-fg\", className)}>{affix.text}</span>;\n}\n\nfunction schemaContainsPath(schema: Schema | undefined, path: string): boolean {\n return (schema ?? []).some(\n (child) => child.props?.href === path || schemaContainsPath(child.schema, path),\n );\n}\n\nconst MenuItemComponent: RendererComponent<\"menu-item\"> = ({ children, node }) => {\n const collapsed = useSidebarCollapsed();\n const { icon, label, prefix, suffix } = node.props;\n const iconOnly = Boolean(icon);\n const flyoutIcon = icon ?? prefix?.icon ?? null;\n const currentPath = usePage().url.split(\"?\")[0];\n const testId = prefixedNodeTestId(\"menu\", node);\n const behavior = useClickBehavior(node.props);\n\n const content = icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <>\n {prefix ? <MenuAffix affix={prefix} /> : null}\n {collapsed ? (\n <span\n className=\"pointer-events-none absolute top-1/2 left-full z-lt-popover ml-2 hidden -translate-y-1/2 rounded-lt-sm border border-lt-border bg-lt-popover px-2 py-1 text-sm whitespace-nowrap text-lt-popover-fg shadow-lt-md group-hover:block\"\n role=\"tooltip\"\n >\n {label}\n </span>\n ) : (\n <span>{label}</span>\n )}\n {suffix ? <MenuAffix affix={suffix} className=\"ml-auto\" /> : null}\n </>\n );\n\n const rowClassName = cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n );\n\n const triggerButton = ({ onClick, processing }: TriggerState) => (\n <button\n aria-label={collapsed || iconOnly ? label : undefined}\n className={rowClassName}\n data-test={testId}\n disabled={processing}\n onClick={onClick}\n type=\"button\"\n >\n {content}\n </button>\n );\n\n if (behavior.kind === \"action\") {\n return (\n <li>\n <ActionTrigger action={behavior.action}>{triggerButton}</ActionTrigger>\n </li>\n );\n }\n\n if (behavior.kind === \"effects\") {\n return <li>{triggerButton({ onClick: behavior.onClick, processing: false })}</li>;\n }\n\n if (behavior.kind === \"navigate\") {\n const active = currentPath === behavior.href;\n\n return (\n <li>\n <Link\n aria-current={active ? \"page\" : undefined}\n aria-label={collapsed || iconOnly ? label : undefined}\n as={behavior.method === \"get\" ? undefined : \"button\"}\n className={cn(rowClassName, active && \"bg-lt-muted font-medium\")}\n data-test={testId}\n href={behavior.href}\n method={behavior.method}\n >\n {content}\n </Link>\n </li>\n );\n }\n\n if (!children) {\n return collapsed ? null : (\n <li>\n <span className=\"flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {content}\n </span>\n </li>\n );\n }\n\n if (collapsed) {\n return (\n <FlyoutGroup icon={flyoutIcon} label={label} testId={testId}>\n {children}\n </FlyoutGroup>\n );\n }\n\n return (\n <CollapsibleItem\n content={content}\n defaultOpen={schemaContainsPath(node.schema, currentPath)}\n testId={testId}\n >\n {children}\n </CollapsibleItem>\n );\n};\n\nfunction CollapsibleItem({\n children,\n content,\n defaultOpen,\n testId,\n}: {\n children: ReactNode;\n content: ReactNode;\n defaultOpen: boolean;\n testId?: string;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n return (\n <li>\n <button\n aria-expanded={open}\n className={cn(rowClass, \"w-full\")}\n data-test={testId}\n onClick={() => setOpen((value) => !value)}\n type=\"button\"\n >\n {content}\n <Icon\n name=\"chevron-right\"\n className={cn(\n \"ml-auto size-lt-icon-md shrink-0 transition-transform\",\n open && \"rotate-90\",\n )}\n />\n </button>\n {open ? <ul className=\"mt-1 flex flex-col gap-1 pl-3\">{children}</ul> : null}\n </li>\n );\n}\n\nfunction FlyoutGroup({\n children,\n icon,\n label,\n testId,\n}: {\n children: ReactNode;\n icon?: string | null;\n label: string;\n testId?: string;\n}) {\n return (\n <li>\n <Popover\n className=\"min-w-48\"\n placement=\"right\"\n testId={testId}\n trigger={\n icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <span>{label}</span>\n )\n }\n triggerClassName={cn(rowClass, \"justify-center\")}\n triggerLabel={label}\n >\n <ul className=\"flex flex-col gap-1\">\n <li className=\"px-3 py-1.5 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {label}\n </li>\n {children}\n </ul>\n </Popover>\n </li>\n );\n}\n\nexport default MenuItemComponent;\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { NotificationsResponse, UnreadCountResponse } from './types';
2
+ export declare function fetchNotifications(endpoint: string, page?: number): Promise<NotificationsResponse>;
3
+ export declare function markRead(endpoint: string, id: string): Promise<UnreadCountResponse>;
4
+ export declare function markAllRead(endpoint: string): Promise<UnreadCountResponse>;
5
+ export declare function dismiss(endpoint: string, id: string): Promise<UnreadCountResponse>;
6
+ export declare function clearAll(endpoint: string): Promise<UnreadCountResponse>;
@@ -0,0 +1,21 @@
1
+ import { apiJson } from "../core/api.js";
2
+ //#region resources/js/notifications/api.ts
3
+ function fetchNotifications(endpoint, page = 1) {
4
+ return apiJson(`${endpoint}?page=${page}`);
5
+ }
6
+ function markRead(endpoint, id) {
7
+ return apiJson(`${endpoint}/${id}/read`, { method: "PATCH" });
8
+ }
9
+ function markAllRead(endpoint) {
10
+ return apiJson(`${endpoint}/read-all`, { method: "POST" });
11
+ }
12
+ function dismiss(endpoint, id) {
13
+ return apiJson(`${endpoint}/${id}`, { method: "DELETE" });
14
+ }
15
+ function clearAll(endpoint) {
16
+ return apiJson(endpoint, { method: "DELETE" });
17
+ }
18
+ //#endregion
19
+ export { clearAll, dismiss, fetchNotifications, markAllRead, markRead };
20
+
21
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","names":[],"sources":["../../resources/js/notifications/api.ts"],"sourcesContent":["import { apiJson } from \"@lattice-php/lattice/core/api\";\nimport type { NotificationsResponse, UnreadCountResponse } from \"./types\";\n\nexport function fetchNotifications(endpoint: string, page = 1): Promise<NotificationsResponse> {\n return apiJson<NotificationsResponse>(`${endpoint}?page=${page}`);\n}\n\nexport function markRead(endpoint: string, id: string): Promise<UnreadCountResponse> {\n return apiJson<UnreadCountResponse>(`${endpoint}/${id}/read`, { method: \"PATCH\" });\n}\n\nexport function markAllRead(endpoint: string): Promise<UnreadCountResponse> {\n return apiJson<UnreadCountResponse>(`${endpoint}/read-all`, { method: \"POST\" });\n}\n\nexport function dismiss(endpoint: string, id: string): Promise<UnreadCountResponse> {\n return apiJson<UnreadCountResponse>(`${endpoint}/${id}`, { method: \"DELETE\" });\n}\n\nexport function clearAll(endpoint: string): Promise<UnreadCountResponse> {\n return apiJson<UnreadCountResponse>(endpoint, { method: \"DELETE\" });\n}\n"],"mappings":";;AAGA,SAAgB,mBAAmB,UAAkB,OAAO,GAAmC;CAC7F,OAAO,QAA+B,GAAG,SAAS,QAAQ,MAAM;AAClE;AAEA,SAAgB,SAAS,UAAkB,IAA0C;CACnF,OAAO,QAA6B,GAAG,SAAS,GAAG,GAAG,QAAQ,EAAE,QAAQ,QAAQ,CAAC;AACnF;AAEA,SAAgB,YAAY,UAAgD;CAC1E,OAAO,QAA6B,GAAG,SAAS,YAAY,EAAE,QAAQ,OAAO,CAAC;AAChF;AAEA,SAAgB,QAAQ,UAAkB,IAA0C;CAClF,OAAO,QAA6B,GAAG,SAAS,GAAG,MAAM,EAAE,QAAQ,SAAS,CAAC;AAC/E;AAEA,SAAgB,SAAS,UAAgD;CACvE,OAAO,QAA6B,UAAU,EAAE,QAAQ,SAAS,CAAC;AACpE"}
@@ -0,0 +1 @@
1
+ export declare function EmptyState(): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { Icon } from "../../icons/sprite.js";
2
+ import { useT } from "../../i18n/instance.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region resources/js/notifications/components/empty-state.tsx
5
+ function EmptyState() {
6
+ const { t } = useT("lattice");
7
+ return /* @__PURE__ */ jsxs("div", {
8
+ className: "flex flex-col items-center gap-2 px-4 py-10 text-lt-muted-fg",
9
+ children: [/* @__PURE__ */ jsx(Icon, {
10
+ name: "bell",
11
+ className: "size-lt-icon-lg"
12
+ }), /* @__PURE__ */ jsx("p", {
13
+ className: "text-sm",
14
+ children: t("notifications.empty", "No notifications yet")
15
+ })]
16
+ });
17
+ }
18
+ //#endregion
19
+ export { EmptyState };
20
+
21
+ //# sourceMappingURL=empty-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state.js","names":[],"sources":["../../../resources/js/notifications/components/empty-state.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\n\nexport function EmptyState() {\n const { t } = useT(\"lattice\");\n return (\n <div className=\"flex flex-col items-center gap-2 px-4 py-10 text-lt-muted-fg\">\n <Icon name=\"bell\" className=\"size-lt-icon-lg\" />\n <p className=\"text-sm\">{t(\"notifications.empty\", \"No notifications yet\")}</p>\n </div>\n );\n}\n"],"mappings":";;;;AAGA,SAAgB,aAAa;CAC3B,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,MAAD;GAAM,MAAK;GAAO,WAAU;EAAmB,CAAA,GAC/C,oBAAC,KAAD;GAAG,WAAU;aAAW,EAAE,uBAAuB,sBAAsB;EAAK,CAAA,CACzE;;AAET"}
@@ -0,0 +1,6 @@
1
+ import { NotificationItem } from '../types';
2
+ export declare function NotificationItemRow({ notification, onMarkRead, onDismiss, }: {
3
+ notification: NotificationItem;
4
+ onMarkRead: (id: string) => void;
5
+ onDismiss: (id: string) => void;
6
+ }): import("react").JSX.Element;