@lattice-php/lattice 0.4.0 → 0.6.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 +3 -3
  2. package/dist/action/components/action-group.js +1 -1
  3. package/dist/action/components/action.js +10 -16
  4. package/dist/action/components/action.js.map +1 -1
  5. package/dist/action/run-action.d.ts +9 -0
  6. package/dist/action/run-action.js +22 -0
  7. package/dist/action/run-action.js.map +1 -0
  8. package/dist/chat/components/chat-box.js +31 -14
  9. package/dist/chat/components/chat-box.js.map +1 -1
  10. package/dist/chat/components/message.js +1 -1
  11. package/dist/chat/components/prompt-input.js +1 -1
  12. package/dist/chat/index.js +2 -0
  13. package/dist/chat/index.js.map +1 -1
  14. package/dist/core/components/badge.js +1 -1
  15. package/dist/core/components/button.js +17 -5
  16. package/dist/core/components/button.js.map +1 -1
  17. package/dist/core/components/card.js +2 -1
  18. package/dist/core/components/card.js.map +1 -1
  19. package/dist/core/components/chart.js +4 -1
  20. package/dist/core/components/chart.js.map +1 -1
  21. package/dist/core/components/dialog.js +1 -1
  22. package/dist/core/components/floating-panel.js +3 -2
  23. package/dist/core/components/floating-panel.js.map +1 -1
  24. package/dist/core/components/fragment.js +3 -6
  25. package/dist/core/components/fragment.js.map +1 -1
  26. package/dist/core/components/grid.js +2 -1
  27. package/dist/core/components/grid.js.map +1 -1
  28. package/dist/core/components/index.js +0 -2
  29. package/dist/core/components/index.js.map +1 -1
  30. package/dist/core/components/section.js +5 -9
  31. package/dist/core/components/section.js.map +1 -1
  32. package/dist/core/components/stack.js +2 -1
  33. package/dist/core/components/stack.js.map +1 -1
  34. package/dist/core/components/tabs.js +1 -1
  35. package/dist/core/nodes.d.ts +6 -0
  36. package/dist/core/nodes.js +13 -0
  37. package/dist/core/nodes.js.map +1 -0
  38. package/dist/core/renderer.js +1 -1
  39. package/dist/create-app.d.ts +21 -0
  40. package/dist/create-app.js +31 -0
  41. package/dist/create-app.js.map +1 -0
  42. package/dist/effects/registry.js +2 -1
  43. package/dist/effects/registry.js.map +1 -1
  44. package/dist/effects/use-effect-dispatcher.js +1 -1
  45. package/dist/effects/use-flash-effects.js +1 -1
  46. package/dist/events/event-names.d.ts +4 -2
  47. package/dist/events/event-names.js +4 -2
  48. package/dist/events/event-names.js.map +1 -1
  49. package/dist/form/components/base/input-otp.d.ts +7 -0
  50. package/dist/form/components/base/input-otp.js +30 -0
  51. package/dist/form/components/base/input-otp.js.map +1 -0
  52. package/dist/form/components/conditions.js +1 -1
  53. package/dist/form/components/conditions.js.map +1 -1
  54. package/dist/form/components/fields/file-upload.js +1 -1
  55. package/dist/form/components/fields/otp-input.d.ts +2 -0
  56. package/dist/form/components/fields/otp-input.js +25 -0
  57. package/dist/form/components/fields/otp-input.js.map +1 -0
  58. package/dist/form/components/fields/row-item.js +1 -1
  59. package/dist/form/components/fields/table-rows.js +1 -1
  60. package/dist/form/components/form.js +1 -1
  61. package/dist/form/components/index.d.ts +1 -0
  62. package/dist/form/components/index.js +2 -1
  63. package/dist/form/components/prefill-targets.d.ts +0 -1
  64. package/dist/form/components/prefill-targets.js +1 -4
  65. package/dist/form/components/prefill-targets.js.map +1 -1
  66. package/dist/form/components/use-form-resolver.js +2 -2
  67. package/dist/form/components/use-form-resolver.js.map +1 -1
  68. package/dist/form/index.js +1 -0
  69. package/dist/form/index.js.map +1 -1
  70. package/dist/i18n/config.d.ts +3 -0
  71. package/dist/i18n/config.js +20 -4
  72. package/dist/i18n/config.js.map +1 -1
  73. package/dist/i18n/date-time.d.ts +7 -0
  74. package/dist/i18n/date-time.js +33 -0
  75. package/dist/i18n/date-time.js.map +1 -0
  76. package/dist/i18n/index.d.ts +4 -0
  77. package/dist/i18n/index.js +3 -1
  78. package/dist/i18n/locale-reload.js +1 -1
  79. package/dist/i18n/page-props.js +1 -1
  80. package/dist/i18n/page-props.js.map +1 -1
  81. package/dist/i18n/timezone.d.ts +7 -0
  82. package/dist/i18n/timezone.js +53 -0
  83. package/dist/i18n/timezone.js.map +1 -0
  84. package/dist/index.d.ts +1 -0
  85. package/dist/index.js +14 -13
  86. package/dist/layout/breadcrumbs.js +3 -2
  87. package/dist/layout/breadcrumbs.js.map +1 -1
  88. package/dist/layout/callouts.js +1 -1
  89. package/dist/layout/components.js +1 -1
  90. package/dist/layout/menu-item.js +2 -2
  91. package/dist/layout/menu.js +2 -1
  92. package/dist/layout/menu.js.map +1 -1
  93. package/dist/layout/popover.js +1 -1
  94. package/dist/layout/sidebar.js +69 -29
  95. package/dist/layout/sidebar.js.map +1 -1
  96. package/dist/layout/topbar.js +2 -1
  97. package/dist/layout/topbar.js.map +1 -1
  98. package/dist/registry.js +1 -1
  99. package/dist/remote/components/data-list.js +1 -1
  100. package/dist/remote/index.js +1 -5
  101. package/dist/remote/index.js.map +1 -1
  102. package/dist/table/components/bulk-bar.js +10 -15
  103. package/dist/table/components/bulk-bar.js.map +1 -1
  104. package/dist/table/components/cells/text-cell.js +46 -4
  105. package/dist/table/components/cells/text-cell.js.map +1 -1
  106. package/dist/table/components/table.js +1 -1
  107. package/dist/table/filter-values.d.ts +5 -0
  108. package/dist/table/filter-values.js +10 -3
  109. package/dist/table/filter-values.js.map +1 -1
  110. package/dist/table/format.d.ts +11 -1
  111. package/dist/table/format.js +23 -21
  112. package/dist/table/format.js.map +1 -1
  113. package/dist/table/query.js +5 -4
  114. package/dist/table/query.js.map +1 -1
  115. package/dist/toast/toaster.js +2 -2
  116. package/dist/types/generated.d.ts +51 -24
  117. package/dist/vite.js +32 -1
  118. package/dist/vite.js.map +1 -1
  119. package/package.json +14 -3
  120. package/dist/remote/components/chat-box.d.ts +0 -3
  121. package/dist/remote/components/chat-box.js +0 -65
  122. package/dist/remote/components/chat-box.js.map +0 -1
@@ -1,45 +1,85 @@
1
+ import { LATTICE_EVENT } from "../events/event-names.js";
1
2
  import { cn } from "../lib/utils.js";
2
- import { Icon } from "../icons/sprite.js";
3
3
  import { CollapsedContext } from "../core/collapsed-context.js";
4
- import { useState } from "react";
4
+ import { nodeIdentity } from "../core/test-id.js";
5
+ import { router } from "@inertiajs/react";
6
+ import { useEffect, useState } from "react";
5
7
  import { jsx, jsxs } from "react/jsx-runtime";
6
8
  //#region resources/js/layout/sidebar.tsx
9
+ var DESKTOP_QUERY = "(min-width: 768px)";
7
10
  function readStored(key, remember) {
8
11
  if (!remember || typeof window === "undefined") return false;
9
12
  return window.localStorage.getItem(key) === "true";
10
13
  }
14
+ function matchesTarget(event, identity) {
15
+ const target = event.detail?.target;
16
+ return target == null || target === identity;
17
+ }
18
+ function useIsDesktop() {
19
+ const supported = () => typeof window !== "undefined" && typeof window.matchMedia === "function";
20
+ const [isDesktop, setIsDesktop] = useState(() => supported() ? window.matchMedia(DESKTOP_QUERY).matches : true);
21
+ useEffect(() => {
22
+ if (!supported()) return;
23
+ const query = window.matchMedia(DESKTOP_QUERY);
24
+ const update = () => setIsDesktop(query.matches);
25
+ update();
26
+ query.addEventListener("change", update);
27
+ return () => query.removeEventListener("change", update);
28
+ }, []);
29
+ return isDesktop;
30
+ }
11
31
  var SidebarComponent = ({ children, node }) => {
12
32
  const collapsible = node.props.collapsible;
13
33
  const rememberState = node.props.rememberState;
14
- const storageKey = `lattice:sidebar:${node.id ?? "default"}`;
34
+ const identity = nodeIdentity(node);
35
+ const storageKey = `lattice:sidebar:${identity ?? "default"}`;
36
+ const isDesktop = useIsDesktop();
15
37
  const [collapsed, setCollapsed] = useState(() => readStored(storageKey, collapsible && rememberState));
16
- function toggle() {
17
- setCollapsed((value) => {
18
- const next = !value;
19
- if (rememberState && typeof window !== "undefined") window.localStorage.setItem(storageKey, String(next));
20
- return next;
21
- });
22
- }
23
- const isCollapsed = collapsible && collapsed;
24
- return /* @__PURE__ */ jsx(CollapsedContext.Provider, {
38
+ const [mobileOpen, setMobileOpen] = useState(false);
39
+ useEffect(() => {
40
+ function toggle(event) {
41
+ if (!matchesTarget(event, identity)) return;
42
+ if (window.matchMedia?.(DESKTOP_QUERY).matches ?? true) {
43
+ if (!collapsible) return;
44
+ setCollapsed((value) => {
45
+ const next = !value;
46
+ if (rememberState && typeof window !== "undefined") window.localStorage.setItem(storageKey, String(next));
47
+ return next;
48
+ });
49
+ } else setMobileOpen((open) => !open);
50
+ }
51
+ window.addEventListener(LATTICE_EVENT.toggleSidebar, toggle);
52
+ return () => window.removeEventListener(LATTICE_EVENT.toggleSidebar, toggle);
53
+ }, [
54
+ identity,
55
+ collapsible,
56
+ rememberState,
57
+ storageKey
58
+ ]);
59
+ useEffect(() => router.on("navigate", () => setMobileOpen(false)), []);
60
+ useEffect(() => {
61
+ if (!mobileOpen) return;
62
+ function close(event) {
63
+ if (event.key === "Escape") setMobileOpen(false);
64
+ }
65
+ window.addEventListener("keydown", close);
66
+ return () => window.removeEventListener("keydown", close);
67
+ }, [mobileOpen]);
68
+ const isCollapsed = collapsible && collapsed && isDesktop;
69
+ return /* @__PURE__ */ jsxs(CollapsedContext.Provider, {
25
70
  value: isCollapsed,
26
- children: /* @__PURE__ */ jsxs("aside", {
27
- className: cn("sticky top-0 flex h-svh shrink-0 flex-col gap-4 border-r border-lt-border p-4 transition-[width]", isCollapsed ? "w-16 overflow-visible" : "w-64 overflow-x-hidden overflow-y-auto"),
28
- "data-lattice-component": node.id,
29
- children: [collapsible ? /* @__PURE__ */ jsx("button", {
30
- "aria-expanded": !isCollapsed,
31
- "aria-label": isCollapsed ? "Expand sidebar" : "Collapse sidebar",
32
- "data-test": "sidebar-toggle",
33
- className: cn("inline-flex items-center rounded-lt-sm p-2 text-lt-fg transition-colors hover:bg-lt-muted", isCollapsed ? "self-center" : "self-end"),
34
- onClick: toggle,
35
- type: "button",
36
- children: /* @__PURE__ */ jsx(Icon, {
37
- name: "panel-left",
38
- "aria-hidden": "true",
39
- className: "size-lt-icon-md shrink-0"
40
- })
41
- }) : null, children]
42
- })
71
+ children: [mobileOpen ? /* @__PURE__ */ jsx("div", {
72
+ "aria-hidden": "true",
73
+ className: "fixed inset-0 z-30 bg-black/50 md:hidden",
74
+ "data-test": "sidebar-backdrop",
75
+ onClick: () => setMobileOpen(false)
76
+ }) : null, /* @__PURE__ */ jsx("aside", {
77
+ className: cn("fixed inset-y-0 left-0 z-40 flex h-svh w-72 max-w-[80vw] shrink-0 flex-col gap-4 border-r border-lt-border bg-lt-bg p-4 transition-transform", "md:sticky md:top-0 md:z-auto md:max-w-none md:translate-x-0 md:transition-[width]", mobileOpen ? "translate-x-0" : "-translate-x-full", isCollapsed ? "md:w-16 md:overflow-visible" : "md:w-64 md:overflow-x-hidden md:overflow-y-auto"),
78
+ "data-collapsed": isCollapsed ? "true" : "false",
79
+ "data-lattice-component": identity,
80
+ "data-test": "sidebar",
81
+ children
82
+ })]
43
83
  });
44
84
  };
45
85
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar.js","names":[],"sources":["../../resources/js/layout/sidebar.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useState } from \"react\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { CollapsedContext } from \"../core/collapsed-context\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\n\nfunction readStored(key: string, remember: boolean): boolean {\n if (!remember || typeof window === \"undefined\") {\n return false;\n }\n\n return window.localStorage.getItem(key) === \"true\";\n}\n\nconst SidebarComponent: RendererComponent<\"sidebar\"> = ({ children, node }) => {\n const collapsible = node.props.collapsible;\n const rememberState = node.props.rememberState;\n const storageKey = `lattice:sidebar:${node.id ?? \"default\"}`;\n\n const [collapsed, setCollapsed] = useState(() =>\n readStored(storageKey, collapsible && rememberState),\n );\n\n function toggle(): void {\n setCollapsed((value) => {\n const next = !value;\n\n if (rememberState && typeof window !== \"undefined\") {\n window.localStorage.setItem(storageKey, String(next));\n }\n\n return next;\n });\n }\n\n const isCollapsed = collapsible && collapsed;\n\n return (\n <CollapsedContext.Provider value={isCollapsed}>\n <aside\n className={cn(\n \"sticky top-0 flex h-svh shrink-0 flex-col gap-4 border-r border-lt-border p-4 transition-[width]\",\n isCollapsed ? \"w-16 overflow-visible\" : \"w-64 overflow-x-hidden overflow-y-auto\",\n )}\n data-lattice-component={node.id}\n >\n {collapsible ? (\n <button\n aria-expanded={!isCollapsed}\n aria-label={isCollapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n data-test=\"sidebar-toggle\"\n className={cn(\n \"inline-flex items-center rounded-lt-sm p-2 text-lt-fg transition-colors hover:bg-lt-muted\",\n isCollapsed ? \"self-center\" : \"self-end\",\n )}\n onClick={toggle}\n type=\"button\"\n >\n <Icon name=\"panel-left\" aria-hidden=\"true\" className=\"size-lt-icon-md shrink-0\" />\n </button>\n ) : null}\n {children}\n </aside>\n </CollapsedContext.Provider>\n );\n};\n\nexport default SidebarComponent;\n"],"mappings":";;;;;;AAMA,SAAS,WAAW,KAAa,UAA4B;CAC3D,IAAI,CAAC,YAAY,OAAO,WAAW,aACjC,OAAO;CAGT,OAAO,OAAO,aAAa,QAAQ,GAAG,MAAM;AAC9C;AAEA,IAAM,oBAAkD,EAAE,UAAU,WAAW;CAC7E,MAAM,cAAc,KAAK,MAAM;CAC/B,MAAM,gBAAgB,KAAK,MAAM;CACjC,MAAM,aAAa,mBAAmB,KAAK,MAAM;CAEjD,MAAM,CAAC,WAAW,gBAAgB,eAChC,WAAW,YAAY,eAAe,aAAa,CACrD;CAEA,SAAS,SAAe;EACtB,cAAc,UAAU;GACtB,MAAM,OAAO,CAAC;GAEd,IAAI,iBAAiB,OAAO,WAAW,aACrC,OAAO,aAAa,QAAQ,YAAY,OAAO,IAAI,CAAC;GAGtD,OAAO;EACT,CAAC;CACH;CAEA,MAAM,cAAc,eAAe;CAEnC,OACE,oBAAC,iBAAiB,UAAlB;EAA2B,OAAO;YAChC,qBAAC,SAAD;GACE,WAAW,GACT,oGACA,cAAc,0BAA0B,wCAC1C;GACA,0BAAwB,KAAK;aAL/B,CAOG,cACC,oBAAC,UAAD;IACE,iBAAe,CAAC;IAChB,cAAY,cAAc,mBAAmB;IAC7C,aAAU;IACV,WAAW,GACT,6FACA,cAAc,gBAAgB,UAChC;IACA,SAAS;IACT,MAAK;cAEL,oBAAC,MAAD;KAAM,MAAK;KAAa,eAAY;KAAO,WAAU;IAA4B,CAAA;GAC3E,CAAA,IACN,MACH,QACI;;CACkB,CAAA;AAE/B"}
1
+ {"version":3,"file":"sidebar.js","names":[],"sources":["../../resources/js/layout/sidebar.tsx"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport { useEffect, useState } from \"react\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { CollapsedContext } from \"../core/collapsed-context\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\n\nconst DESKTOP_QUERY = \"(min-width: 768px)\";\n\nfunction readStored(key: string, remember: boolean): boolean {\n if (!remember || typeof window === \"undefined\") {\n return false;\n }\n\n return window.localStorage.getItem(key) === \"true\";\n}\n\nfunction matchesTarget(event: Event, identity: string | undefined): boolean {\n const target = (event as CustomEvent<{ target?: string }>).detail?.target;\n\n return target == null || target === identity;\n}\n\nfunction useIsDesktop(): boolean {\n const supported = () => typeof window !== \"undefined\" && typeof window.matchMedia === \"function\";\n const [isDesktop, setIsDesktop] = useState(() =>\n supported() ? window.matchMedia(DESKTOP_QUERY).matches : true,\n );\n\n useEffect(() => {\n if (!supported()) {\n return;\n }\n\n const query = window.matchMedia(DESKTOP_QUERY);\n const update = (): void => setIsDesktop(query.matches);\n\n update();\n query.addEventListener(\"change\", update);\n\n return () => query.removeEventListener(\"change\", update);\n }, []);\n\n return isDesktop;\n}\n\nconst SidebarComponent: RendererComponent<\"sidebar\"> = ({ children, node }) => {\n const collapsible = node.props.collapsible;\n const rememberState = node.props.rememberState;\n const identity = nodeIdentity(node);\n const storageKey = `lattice:sidebar:${identity ?? \"default\"}`;\n const isDesktop = useIsDesktop();\n\n const [collapsed, setCollapsed] = useState(() =>\n readStored(storageKey, collapsible && rememberState),\n );\n const [mobileOpen, setMobileOpen] = useState(false);\n\n useEffect(() => {\n function toggle(event: Event): void {\n if (!matchesTarget(event, identity)) {\n return;\n }\n\n if (window.matchMedia?.(DESKTOP_QUERY).matches ?? true) {\n if (!collapsible) {\n return;\n }\n\n setCollapsed((value) => {\n const next = !value;\n\n if (rememberState && typeof window !== \"undefined\") {\n window.localStorage.setItem(storageKey, String(next));\n }\n\n return next;\n });\n } else {\n setMobileOpen((open) => !open);\n }\n }\n\n window.addEventListener(LATTICE_EVENT.toggleSidebar, toggle);\n\n return () => window.removeEventListener(LATTICE_EVENT.toggleSidebar, toggle);\n }, [identity, collapsible, rememberState, storageKey]);\n\n useEffect(() => router.on(\"navigate\", () => setMobileOpen(false)), []);\n\n useEffect(() => {\n if (!mobileOpen) {\n return;\n }\n\n function close(event: KeyboardEvent): void {\n if (event.key === \"Escape\") {\n setMobileOpen(false);\n }\n }\n\n window.addEventListener(\"keydown\", close);\n\n return () => window.removeEventListener(\"keydown\", close);\n }, [mobileOpen]);\n\n const isCollapsed = collapsible && collapsed && isDesktop;\n\n return (\n <CollapsedContext.Provider value={isCollapsed}>\n {mobileOpen ? (\n <div\n aria-hidden=\"true\"\n className=\"fixed inset-0 z-30 bg-black/50 md:hidden\"\n data-test=\"sidebar-backdrop\"\n onClick={() => setMobileOpen(false)}\n />\n ) : null}\n <aside\n className={cn(\n \"fixed inset-y-0 left-0 z-40 flex h-svh w-72 max-w-[80vw] shrink-0 flex-col gap-4 border-r border-lt-border bg-lt-bg p-4 transition-transform\",\n \"md:sticky md:top-0 md:z-auto md:max-w-none md:translate-x-0 md:transition-[width]\",\n mobileOpen ? \"translate-x-0\" : \"-translate-x-full\",\n isCollapsed\n ? \"md:w-16 md:overflow-visible\"\n : \"md:w-64 md:overflow-x-hidden md:overflow-y-auto\",\n )}\n data-collapsed={isCollapsed ? \"true\" : \"false\"}\n data-lattice-component={identity}\n data-test=\"sidebar\"\n >\n {children}\n </aside>\n </CollapsedContext.Provider>\n );\n};\n\nexport default SidebarComponent;\n"],"mappings":";;;;;;;;AAQA,IAAM,gBAAgB;AAEtB,SAAS,WAAW,KAAa,UAA4B;CAC3D,IAAI,CAAC,YAAY,OAAO,WAAW,aACjC,OAAO;CAGT,OAAO,OAAO,aAAa,QAAQ,GAAG,MAAM;AAC9C;AAEA,SAAS,cAAc,OAAc,UAAuC;CAC1E,MAAM,SAAU,MAA2C,QAAQ;CAEnE,OAAO,UAAU,QAAQ,WAAW;AACtC;AAEA,SAAS,eAAwB;CAC/B,MAAM,kBAAkB,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe;CACtF,MAAM,CAAC,WAAW,gBAAgB,eAChC,UAAU,IAAI,OAAO,WAAW,aAAa,EAAE,UAAU,IAC3D;CAEA,gBAAgB;EACd,IAAI,CAAC,UAAU,GACb;EAGF,MAAM,QAAQ,OAAO,WAAW,aAAa;EAC7C,MAAM,eAAqB,aAAa,MAAM,OAAO;EAErD,OAAO;EACP,MAAM,iBAAiB,UAAU,MAAM;EAEvC,aAAa,MAAM,oBAAoB,UAAU,MAAM;CACzD,GAAG,CAAC,CAAC;CAEL,OAAO;AACT;AAEA,IAAM,oBAAkD,EAAE,UAAU,WAAW;CAC7E,MAAM,cAAc,KAAK,MAAM;CAC/B,MAAM,gBAAgB,KAAK,MAAM;CACjC,MAAM,WAAW,aAAa,IAAI;CAClC,MAAM,aAAa,mBAAmB,YAAY;CAClD,MAAM,YAAY,aAAa;CAE/B,MAAM,CAAC,WAAW,gBAAgB,eAChC,WAAW,YAAY,eAAe,aAAa,CACrD;CACA,MAAM,CAAC,YAAY,iBAAiB,SAAS,KAAK;CAElD,gBAAgB;EACd,SAAS,OAAO,OAAoB;GAClC,IAAI,CAAC,cAAc,OAAO,QAAQ,GAChC;GAGF,IAAI,OAAO,aAAa,aAAa,EAAE,WAAW,MAAM;IACtD,IAAI,CAAC,aACH;IAGF,cAAc,UAAU;KACtB,MAAM,OAAO,CAAC;KAEd,IAAI,iBAAiB,OAAO,WAAW,aACrC,OAAO,aAAa,QAAQ,YAAY,OAAO,IAAI,CAAC;KAGtD,OAAO;IACT,CAAC;GACH,OACE,eAAe,SAAS,CAAC,IAAI;EAEjC;EAEA,OAAO,iBAAiB,cAAc,eAAe,MAAM;EAE3D,aAAa,OAAO,oBAAoB,cAAc,eAAe,MAAM;CAC7E,GAAG;EAAC;EAAU;EAAa;EAAe;CAAU,CAAC;CAErD,gBAAgB,OAAO,GAAG,kBAAkB,cAAc,KAAK,CAAC,GAAG,CAAC,CAAC;CAErE,gBAAgB;EACd,IAAI,CAAC,YACH;EAGF,SAAS,MAAM,OAA4B;GACzC,IAAI,MAAM,QAAQ,UAChB,cAAc,KAAK;EAEvB;EAEA,OAAO,iBAAiB,WAAW,KAAK;EAExC,aAAa,OAAO,oBAAoB,WAAW,KAAK;CAC1D,GAAG,CAAC,UAAU,CAAC;CAEf,MAAM,cAAc,eAAe,aAAa;CAEhD,OACE,qBAAC,iBAAiB,UAAlB;EAA2B,OAAO;YAAlC,CACG,aACC,oBAAC,OAAD;GACE,eAAY;GACZ,WAAU;GACV,aAAU;GACV,eAAe,cAAc,KAAK;EACnC,CAAA,IACC,MACJ,oBAAC,SAAD;GACE,WAAW,GACT,gJACA,qFACA,aAAa,kBAAkB,qBAC/B,cACI,gCACA,iDACN;GACA,kBAAgB,cAAc,SAAS;GACvC,0BAAwB;GACxB,aAAU;GAET;EACI,CAAA,CACkB;;AAE/B"}
@@ -1,10 +1,11 @@
1
1
  import { cn } from "../lib/utils.js";
2
+ import { nodeIdentity } from "../core/test-id.js";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
  //#region resources/js/layout/topbar.tsx
4
5
  var TopbarComponent = ({ children, node }) => {
5
6
  const sticky = node.props.sticky;
6
7
  return /* @__PURE__ */ jsx("header", {
7
- "data-lattice-component": node.id,
8
+ "data-lattice-component": nodeIdentity(node),
8
9
  className: cn("flex h-14 w-full items-center gap-2 border-b border-lt-border bg-lt-bg px-4 text-lt-fg", sticky && "sticky top-0 z-30"),
9
10
  children
10
11
  });
@@ -1 +1 @@
1
- {"version":3,"file":"topbar.js","names":[],"sources":["../../resources/js/layout/topbar.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\n\nconst TopbarComponent: RendererComponent<\"topbar\"> = ({ children, node }) => {\n const sticky = node.props.sticky;\n\n return (\n <header\n data-lattice-component={node.id}\n className={cn(\n \"flex h-14 w-full items-center gap-2 border-b border-lt-border bg-lt-bg px-4 text-lt-fg\",\n sticky && \"sticky top-0 z-30\",\n )}\n >\n {children}\n </header>\n );\n};\n\nexport default TopbarComponent;\n"],"mappings":";;;AAGA,IAAM,mBAAgD,EAAE,UAAU,WAAW;CAC3E,MAAM,SAAS,KAAK,MAAM;CAE1B,OACE,oBAAC,UAAD;EACE,0BAAwB,KAAK;EAC7B,WAAW,GACT,0FACA,UAAU,mBACZ;EAEC;CACK,CAAA;AAEZ"}
1
+ {"version":3,"file":"topbar.js","names":[],"sources":["../../resources/js/layout/topbar.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\n\nconst TopbarComponent: RendererComponent<\"topbar\"> = ({ children, node }) => {\n const sticky = node.props.sticky;\n\n return (\n <header\n data-lattice-component={nodeIdentity(node)}\n className={cn(\n \"flex h-14 w-full items-center gap-2 border-b border-lt-border bg-lt-bg px-4 text-lt-fg\",\n sticky && \"sticky top-0 z-30\",\n )}\n >\n {children}\n </header>\n );\n};\n\nexport default TopbarComponent;\n"],"mappings":";;;;AAIA,IAAM,mBAAgD,EAAE,UAAU,WAAW;CAC3E,MAAM,SAAS,KAAK,MAAM;CAE1B,OACE,oBAAC,UAAD;EACE,0BAAwB,aAAa,IAAI;EACzC,WAAW,GACT,0FACA,UAAU,mBACZ;EAEC;CACK,CAAA;AAEZ"}
package/dist/registry.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createRegistry } from "./core/registry.js";
2
+ import { layoutComponents } from "./layout/components.js";
2
3
  import { actionComponents } from "./action/index.js";
3
4
  import { chatPlugin } from "./chat/index.js";
4
5
  import { coreComponents } from "./core/components/index.js";
5
6
  import { formComponents } from "./form/index.js";
6
7
  import { remoteComponents } from "./remote/index.js";
7
- import { layoutComponents } from "./layout/components.js";
8
8
  import { tableComponents } from "./table/index.js";
9
9
  //#region resources/js/registry.ts
10
10
  var registry = createRegistry(coreComponents, actionComponents, formComponents, layoutComponents, tableComponents, chatPlugin, remoteComponents);
@@ -1,6 +1,6 @@
1
+ import { Renderer } from "../../core/renderer.js";
1
2
  import { useT } from "../../i18n/instance.js";
2
3
  import { remoteJson } from "../../core/api.js";
3
- import { Renderer } from "../../core/renderer.js";
4
4
  import { useEffect, useState } from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region resources/js/remote/components/data-list.tsx
@@ -1,12 +1,8 @@
1
1
  import { createPlugin, eagerComponent } from "../core/registry.js";
2
2
  import { DataList } from "./components/data-list.js";
3
- import { RemoteChatBox } from "./components/chat-box.js";
4
3
  //#region resources/js/remote/index.ts
5
4
  var remoteComponents = createPlugin({
6
- components: {
7
- "remote.data-list": eagerComponent(DataList),
8
- "remote.chat-box": eagerComponent(RemoteChatBox)
9
- },
5
+ components: { "remote.data-list": eagerComponent(DataList) },
10
6
  name: "lattice/remote"
11
7
  });
12
8
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../resources/js/remote/index.ts"],"sourcesContent":["import { createPlugin, eagerComponent } from \"@lattice-php/lattice/core/registry\";\nimport DataListComponent from \"./components/data-list\";\nimport RemoteChatBoxComponent from \"./components/chat-box\";\n\nexport const remoteComponents = createPlugin({\n components: {\n \"remote.data-list\": eagerComponent(DataListComponent),\n \"remote.chat-box\": eagerComponent(RemoteChatBoxComponent),\n },\n name: \"lattice/remote\",\n});\n"],"mappings":";;;;AAIA,IAAa,mBAAmB,aAAa;CAC3C,YAAY;EACV,oBAAoB,eAAe,QAAiB;EACpD,mBAAmB,eAAe,aAAsB;CAC1D;CACA,MAAM;AACR,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../resources/js/remote/index.ts"],"sourcesContent":["import { createPlugin, eagerComponent } from \"@lattice-php/lattice/core/registry\";\nimport DataListComponent from \"./components/data-list\";\n\nexport const remoteComponents = createPlugin({\n components: {\n \"remote.data-list\": eagerComponent(DataListComponent),\n },\n name: \"lattice/remote\",\n});\n"],"mappings":";;;AAGA,IAAa,mBAAmB,aAAa;CAC3C,YAAY,EACV,oBAAoB,eAAe,QAAiB,EACtD;CACA,MAAM;AACR,CAAC"}
@@ -1,14 +1,15 @@
1
1
  import { withHeaders } from "../../core/headers.js";
2
+ import { useT } from "../../i18n/instance.js";
2
3
  import { prefixedTestId } from "../../core/test-id.js";
4
+ import { getActionEffects } from "../../effects/dispatch.js";
5
+ import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
3
6
  import { Button } from "../../core/components/button.js";
4
- import { useT } from "../../i18n/instance.js";
5
7
  import { Spinner } from "../../core/components/spinner.js";
6
8
  import { ConfirmDialog } from "../../core/components/confirm-dialog.js";
7
- import { dispatchActionError, getActionEffects } from "../../effects/dispatch.js";
8
- import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
9
+ import { runAction } from "../../action/run-action.js";
9
10
  import { ActionForm } from "../../action/components/action-form.js";
10
- import { useState } from "react";
11
11
  import { useHttp } from "@inertiajs/react";
12
+ import { useState } from "react";
12
13
  import { jsx, jsxs } from "react/jsx-runtime";
13
14
  //#region resources/js/table/components/bulk-bar.tsx
14
15
  function BulkBar({ actions, selectedKeys, allMatching, total, query, canSelectAllMatching, onSelectAllMatching, onCompleted }) {
@@ -22,19 +23,13 @@ function BulkBar({ actions, selectedKeys, allMatching, total, query, canSelectAl
22
23
  ...query
23
24
  } : { selected: selectedKeys };
24
25
  async function submit(action) {
25
- try {
26
- http.transform((data) => ({
27
- ...data,
28
- ...allMatching ? {
29
- allMatching: true,
30
- ...query
31
- } : { selected: selectedKeys }
32
- }));
33
- dispatch(getActionEffects((await http[action.method](action.endpoint, { headers: withHeaders(action.ref ?? "") })).effects));
26
+ http.transform((data) => ({
27
+ ...data,
28
+ ...selectionPayload()
29
+ }));
30
+ if (await runAction(() => http[action.method](action.endpoint, { headers: withHeaders(action.ref ?? "") }), dispatch)) {
34
31
  setConfirming(null);
35
32
  onCompleted();
36
- } catch (error) {
37
- dispatchActionError(error);
38
33
  }
39
34
  }
40
35
  function run(action) {
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-bar.js","names":[],"sources":["../../../resources/js/table/components/bulk-bar.tsx"],"sourcesContent":["import { useHttp } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport { ActionForm } from \"@lattice-php/lattice/action/components/action-form\";\nimport { dispatchActionError, getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport { Button } from \"@lattice-php/lattice/core/components/button\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/core/components/confirm-dialog\";\nimport { Spinner } from \"@lattice-php/lattice/core/components/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type { BulkAction } from \"../bulk\";\n\ntype BulkData = {\n allMatching?: boolean;\n selected?: string[];\n};\n\nexport function BulkBar({\n actions,\n selectedKeys,\n allMatching,\n total,\n query,\n canSelectAllMatching,\n onSelectAllMatching,\n onCompleted,\n}: {\n actions: BulkAction[];\n selectedKeys: string[];\n allMatching: boolean;\n total?: number;\n query: Record<string, unknown>;\n canSelectAllMatching: boolean;\n onSelectAllMatching: () => void;\n onCompleted: () => void;\n}) {\n const { t } = useT(\"lattice\");\n const http = useHttp<BulkData, ActionResponse>({});\n const dispatch = useEffectDispatcher();\n const [confirming, setConfirming] = useState<BulkAction | null>(null);\n const [filling, setFilling] = useState<BulkAction | null>(null);\n\n const selectionPayload = (): Record<string, unknown> =>\n allMatching ? { allMatching: true, ...query } : { selected: selectedKeys };\n\n async function submit(action: BulkAction): Promise<void> {\n try {\n http.transform((data) => ({\n ...data,\n ...(allMatching ? { allMatching: true, ...query } : { selected: selectedKeys }),\n }));\n\n const response = await http[action.method](action.endpoint, {\n headers: withHeaders(action.ref ?? \"\"),\n });\n\n dispatch(getActionEffects(response.effects));\n setConfirming(null);\n onCompleted();\n } catch (error) {\n dispatchActionError(error);\n }\n }\n\n function run(action: BulkAction): void {\n if (action.form) {\n setFilling(action);\n\n return;\n }\n\n if (action.confirmation) {\n setConfirming(action);\n\n return;\n }\n\n void submit(action);\n }\n\n const count = allMatching ? (total ?? selectedKeys.length) : selectedKeys.length;\n\n return (\n <div className=\"flex flex-wrap items-center gap-3 border-b border-lt-border bg-lt-muted/50 p-4 text-sm\">\n <span className=\"font-medium\">\n {allMatching\n ? t(\"bulk.allSelected\", \"All {{count}} selected\", { count })\n : t(\"bulk.selected\", \"{{count}} selected\", { count })}\n </span>\n {canSelectAllMatching && (\n <button\n type=\"button\"\n data-test=\"bulk-select-all-matching\"\n className=\"font-medium text-lt-primary underline underline-offset-2\"\n onClick={onSelectAllMatching}\n >\n {t(\"bulk.selectAllMatching\", \"Select all {{total}} matching\", { total })}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-2\">\n {actions.map((action) => (\n <Button\n key={action.id}\n type=\"button\"\n data-test={prefixedTestId(\"bulk-action\", action.id)}\n variant={action.variant}\n disabled={http.processing}\n onClick={() => run(action)}\n >\n {http.processing && <Spinner />}\n {action.label}\n </Button>\n ))}\n </div>\n\n {confirming?.confirmation && (\n <ConfirmDialog\n title={confirming.confirmation.title ?? confirming.label}\n description={confirming.confirmation.description ?? undefined}\n confirmLabel={confirming.confirmation.confirmLabel ?? confirming.label}\n cancelLabel={confirming.confirmation.cancelLabel ?? \"Cancel\"}\n confirmVariant={confirming.variant}\n processing={http.processing}\n onConfirm={() => void submit(confirming)}\n onCancel={() => setConfirming(null)}\n />\n )}\n\n {filling?.form && (\n <ActionForm\n cancelLabel={filling.confirmation?.cancelLabel ?? \"Cancel\"}\n componentRef={filling.ref}\n description={filling.confirmation?.description ?? undefined}\n endpoint={filling.endpoint}\n extraData={selectionPayload()}\n formNode={filling.form}\n method={filling.method}\n onClose={() => setFilling(null)}\n onSuccess={(response) => {\n dispatch(getActionEffects(response.effects));\n setFilling(null);\n onCompleted();\n }}\n submitLabel={filling.confirmation?.confirmLabel ?? filling.label}\n title={filling.confirmation?.title ?? filling.label}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAmBA,SAAgB,QAAQ,EACtB,SACA,cACA,aACA,OACA,OACA,sBACA,qBACA,eAUC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,OAAO,QAAkC,CAAC,CAAC;CACjD,MAAM,WAAW,oBAAoB;CACrC,MAAM,CAAC,YAAY,iBAAiB,SAA4B,IAAI;CACpE,MAAM,CAAC,SAAS,cAAc,SAA4B,IAAI;CAE9D,MAAM,yBACJ,cAAc;EAAE,aAAa;EAAM,GAAG;CAAM,IAAI,EAAE,UAAU,aAAa;CAE3E,eAAe,OAAO,QAAmC;EACvD,IAAI;GACF,KAAK,WAAW,UAAU;IACxB,GAAG;IACH,GAAI,cAAc;KAAE,aAAa;KAAM,GAAG;IAAM,IAAI,EAAE,UAAU,aAAa;GAC/E,EAAE;GAMF,SAAS,kBAAiB,MAJH,KAAK,OAAO,QAAQ,OAAO,UAAU,EAC1D,SAAS,YAAY,OAAO,OAAO,EAAE,EACvC,CAAC,GAEkC,OAAO,CAAC;GAC3C,cAAc,IAAI;GAClB,YAAY;EACd,SAAS,OAAO;GACd,oBAAoB,KAAK;EAC3B;CACF;CAEA,SAAS,IAAI,QAA0B;EACrC,IAAI,OAAO,MAAM;GACf,WAAW,MAAM;GAEjB;EACF;EAEA,IAAI,OAAO,cAAc;GACvB,cAAc,MAAM;GAEpB;EACF;EAEA,OAAY,MAAM;CACpB;CAEA,MAAM,QAAQ,cAAe,SAAS,aAAa,SAAU,aAAa;CAE1E,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,QAAD;IAAM,WAAU;cACb,cACG,EAAE,oBAAoB,0BAA0B,EAAE,MAAM,CAAC,IACzD,EAAE,iBAAiB,sBAAsB,EAAE,MAAM,CAAC;GAClD,CAAA;GACL,wBACC,oBAAC,UAAD;IACE,MAAK;IACL,aAAU;IACV,WAAU;IACV,SAAS;cAER,EAAE,0BAA0B,iCAAiC,EAAE,MAAM,CAAC;GACjE,CAAA;GAEV,oBAAC,OAAD;IAAK,WAAU;cACZ,QAAQ,KAAK,WACZ,qBAAC,QAAD;KAEE,MAAK;KACL,aAAW,eAAe,eAAe,OAAO,EAAE;KAClD,SAAS,OAAO;KAChB,UAAU,KAAK;KACf,eAAe,IAAI,MAAM;eAN3B,CAQG,KAAK,cAAc,oBAAC,SAAD,CAAU,CAAA,GAC7B,OAAO,KACF;OATD,OAAO,EASN,CACT;GACE,CAAA;GAEJ,YAAY,gBACX,oBAAC,eAAD;IACE,OAAO,WAAW,aAAa,SAAS,WAAW;IACnD,aAAa,WAAW,aAAa,eAAe,KAAA;IACpD,cAAc,WAAW,aAAa,gBAAgB,WAAW;IACjE,aAAa,WAAW,aAAa,eAAe;IACpD,gBAAgB,WAAW;IAC3B,YAAY,KAAK;IACjB,iBAAiB,KAAK,OAAO,UAAU;IACvC,gBAAgB,cAAc,IAAI;GACnC,CAAA;GAGF,SAAS,QACR,oBAAC,YAAD;IACE,aAAa,QAAQ,cAAc,eAAe;IAClD,cAAc,QAAQ;IACtB,aAAa,QAAQ,cAAc,eAAe,KAAA;IAClD,UAAU,QAAQ;IAClB,WAAW,iBAAiB;IAC5B,UAAU,QAAQ;IAClB,QAAQ,QAAQ;IAChB,eAAe,WAAW,IAAI;IAC9B,YAAY,aAAa;KACvB,SAAS,iBAAiB,SAAS,OAAO,CAAC;KAC3C,WAAW,IAAI;KACf,YAAY;IACd;IACA,aAAa,QAAQ,cAAc,gBAAgB,QAAQ;IAC3D,OAAO,QAAQ,cAAc,SAAS,QAAQ;GAC/C,CAAA;EAEA;;AAET"}
1
+ {"version":3,"file":"bulk-bar.js","names":[],"sources":["../../../resources/js/table/components/bulk-bar.tsx"],"sourcesContent":["import { useHttp } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport { ActionForm } from \"@lattice-php/lattice/action/components/action-form\";\nimport { runAction } from \"@lattice-php/lattice/action/run-action\";\nimport { getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport { Button } from \"@lattice-php/lattice/core/components/button\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/core/components/confirm-dialog\";\nimport { Spinner } from \"@lattice-php/lattice/core/components/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type { BulkAction } from \"../bulk\";\n\ntype BulkData = {\n allMatching?: boolean;\n selected?: string[];\n};\n\nexport function BulkBar({\n actions,\n selectedKeys,\n allMatching,\n total,\n query,\n canSelectAllMatching,\n onSelectAllMatching,\n onCompleted,\n}: {\n actions: BulkAction[];\n selectedKeys: string[];\n allMatching: boolean;\n total?: number;\n query: Record<string, unknown>;\n canSelectAllMatching: boolean;\n onSelectAllMatching: () => void;\n onCompleted: () => void;\n}) {\n const { t } = useT(\"lattice\");\n const http = useHttp<BulkData, ActionResponse>({});\n const dispatch = useEffectDispatcher();\n const [confirming, setConfirming] = useState<BulkAction | null>(null);\n const [filling, setFilling] = useState<BulkAction | null>(null);\n\n const selectionPayload = (): Record<string, unknown> =>\n allMatching ? { allMatching: true, ...query } : { selected: selectedKeys };\n\n async function submit(action: BulkAction): Promise<void> {\n http.transform((data) => ({ ...data, ...selectionPayload() }));\n\n const ok = await runAction(\n () => http[action.method](action.endpoint, { headers: withHeaders(action.ref ?? \"\") }),\n dispatch,\n );\n\n if (ok) {\n setConfirming(null);\n onCompleted();\n }\n }\n\n function run(action: BulkAction): void {\n if (action.form) {\n setFilling(action);\n\n return;\n }\n\n if (action.confirmation) {\n setConfirming(action);\n\n return;\n }\n\n void submit(action);\n }\n\n const count = allMatching ? (total ?? selectedKeys.length) : selectedKeys.length;\n\n return (\n <div className=\"flex flex-wrap items-center gap-3 border-b border-lt-border bg-lt-muted/50 p-4 text-sm\">\n <span className=\"font-medium\">\n {allMatching\n ? t(\"bulk.allSelected\", \"All {{count}} selected\", { count })\n : t(\"bulk.selected\", \"{{count}} selected\", { count })}\n </span>\n {canSelectAllMatching && (\n <button\n type=\"button\"\n data-test=\"bulk-select-all-matching\"\n className=\"font-medium text-lt-primary underline underline-offset-2\"\n onClick={onSelectAllMatching}\n >\n {t(\"bulk.selectAllMatching\", \"Select all {{total}} matching\", { total })}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-2\">\n {actions.map((action) => (\n <Button\n key={action.id}\n type=\"button\"\n data-test={prefixedTestId(\"bulk-action\", action.id)}\n variant={action.variant}\n disabled={http.processing}\n onClick={() => run(action)}\n >\n {http.processing && <Spinner />}\n {action.label}\n </Button>\n ))}\n </div>\n\n {confirming?.confirmation && (\n <ConfirmDialog\n title={confirming.confirmation.title ?? confirming.label}\n description={confirming.confirmation.description ?? undefined}\n confirmLabel={confirming.confirmation.confirmLabel ?? confirming.label}\n cancelLabel={confirming.confirmation.cancelLabel ?? \"Cancel\"}\n confirmVariant={confirming.variant}\n processing={http.processing}\n onConfirm={() => void submit(confirming)}\n onCancel={() => setConfirming(null)}\n />\n )}\n\n {filling?.form && (\n <ActionForm\n cancelLabel={filling.confirmation?.cancelLabel ?? \"Cancel\"}\n componentRef={filling.ref}\n description={filling.confirmation?.description ?? undefined}\n endpoint={filling.endpoint}\n extraData={selectionPayload()}\n formNode={filling.form}\n method={filling.method}\n onClose={() => setFilling(null)}\n onSuccess={(response) => {\n dispatch(getActionEffects(response.effects));\n setFilling(null);\n onCompleted();\n }}\n submitLabel={filling.confirmation?.confirmLabel ?? filling.label}\n title={filling.confirmation?.title ?? filling.label}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAoBA,SAAgB,QAAQ,EACtB,SACA,cACA,aACA,OACA,OACA,sBACA,qBACA,eAUC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,OAAO,QAAkC,CAAC,CAAC;CACjD,MAAM,WAAW,oBAAoB;CACrC,MAAM,CAAC,YAAY,iBAAiB,SAA4B,IAAI;CACpE,MAAM,CAAC,SAAS,cAAc,SAA4B,IAAI;CAE9D,MAAM,yBACJ,cAAc;EAAE,aAAa;EAAM,GAAG;CAAM,IAAI,EAAE,UAAU,aAAa;CAE3E,eAAe,OAAO,QAAmC;EACvD,KAAK,WAAW,UAAU;GAAE,GAAG;GAAM,GAAG,iBAAiB;EAAE,EAAE;EAO7D,IAAI,MALa,gBACT,KAAK,OAAO,QAAQ,OAAO,UAAU,EAAE,SAAS,YAAY,OAAO,OAAO,EAAE,EAAE,CAAC,GACrF,QACF,GAEQ;GACN,cAAc,IAAI;GAClB,YAAY;EACd;CACF;CAEA,SAAS,IAAI,QAA0B;EACrC,IAAI,OAAO,MAAM;GACf,WAAW,MAAM;GAEjB;EACF;EAEA,IAAI,OAAO,cAAc;GACvB,cAAc,MAAM;GAEpB;EACF;EAEA,OAAY,MAAM;CACpB;CAEA,MAAM,QAAQ,cAAe,SAAS,aAAa,SAAU,aAAa;CAE1E,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,QAAD;IAAM,WAAU;cACb,cACG,EAAE,oBAAoB,0BAA0B,EAAE,MAAM,CAAC,IACzD,EAAE,iBAAiB,sBAAsB,EAAE,MAAM,CAAC;GAClD,CAAA;GACL,wBACC,oBAAC,UAAD;IACE,MAAK;IACL,aAAU;IACV,WAAU;IACV,SAAS;cAER,EAAE,0BAA0B,iCAAiC,EAAE,MAAM,CAAC;GACjE,CAAA;GAEV,oBAAC,OAAD;IAAK,WAAU;cACZ,QAAQ,KAAK,WACZ,qBAAC,QAAD;KAEE,MAAK;KACL,aAAW,eAAe,eAAe,OAAO,EAAE;KAClD,SAAS,OAAO;KAChB,UAAU,KAAK;KACf,eAAe,IAAI,MAAM;eAN3B,CAQG,KAAK,cAAc,oBAAC,SAAD,CAAU,CAAA,GAC7B,OAAO,KACF;OATD,OAAO,EASN,CACT;GACE,CAAA;GAEJ,YAAY,gBACX,oBAAC,eAAD;IACE,OAAO,WAAW,aAAa,SAAS,WAAW;IACnD,aAAa,WAAW,aAAa,eAAe,KAAA;IACpD,cAAc,WAAW,aAAa,gBAAgB,WAAW;IACjE,aAAa,WAAW,aAAa,eAAe;IACpD,gBAAgB,WAAW;IAC3B,YAAY,KAAK;IACjB,iBAAiB,KAAK,OAAO,UAAU;IACvC,gBAAgB,cAAc,IAAI;GACnC,CAAA;GAGF,SAAS,QACR,oBAAC,YAAD;IACE,aAAa,QAAQ,cAAc,eAAe;IAClD,cAAc,QAAQ;IACtB,aAAa,QAAQ,cAAc,eAAe,KAAA;IAClD,UAAU,QAAQ;IAClB,WAAW,iBAAiB;IAC5B,UAAU,QAAQ;IAClB,QAAQ,QAAQ;IAChB,eAAe,WAAW,IAAI;IAC9B,YAAY,aAAa;KACvB,SAAS,iBAAiB,SAAS,OAAO,CAAC;KAC3C,WAAW,IAAI;KACf,YAAY;IACd;IACA,aAAa,QAAQ,cAAc,gBAAgB,QAAQ;IAC3D,OAAO,QAAQ,cAAc,SAAS,QAAQ;GAC/C,CAAA;EAEA;;AAET"}
@@ -1,13 +1,50 @@
1
+ import { cn } from "../../../lib/utils.js";
1
2
  import { Icon } from "../../../icons/sprite.js";
2
- import { copyToClipboard } from "../../../clipboard/index.js";
3
3
  import { formatCell, resolveLink } from "../../format.js";
4
+ import { DateTime } from "../../../i18n/date-time.js";
5
+ import { copyToClipboard } from "../../../clipboard/index.js";
4
6
  import { useEffect, useState } from "react";
5
7
  import { jsx, jsxs } from "react/jsx-runtime";
6
8
  //#region resources/js/table/components/cells/text-cell.tsx
7
- var TextCell = ({ column, props, row, value }) => {
9
+ var TextCell = (args) => {
10
+ if (args.props.multiple) return /* @__PURE__ */ jsx(MultipleCell, { ...args });
11
+ if (args.props.badge) return /* @__PURE__ */ jsx(SingleBadgeCell, { ...args });
12
+ return /* @__PURE__ */ jsx(PlainTextCell, { ...args });
13
+ };
14
+ function Badge({ label, color }) {
15
+ if (label === "") return null;
16
+ return /* @__PURE__ */ jsx("span", {
17
+ className: cn("lt-cell-badge", `lt-cell-tone-${color || "gray"}`),
18
+ children: label
19
+ });
20
+ }
21
+ function MultipleCell({ column, props, value }) {
22
+ const items = Array.isArray(value) ? value : [];
23
+ if (items.length === 0) return null;
24
+ if (!props.badge) return /* @__PURE__ */ jsx("span", { children: items.map((item) => formatCell(item, column)).join(", ") });
25
+ return /* @__PURE__ */ jsx("div", {
26
+ className: "flex flex-wrap gap-1",
27
+ children: items.map((item, index) => {
28
+ const chip = item;
29
+ return /* @__PURE__ */ jsx(Badge, {
30
+ label: formatCell(chip.value, column),
31
+ color: chip.color
32
+ }, index);
33
+ })
34
+ });
35
+ }
36
+ function SingleBadgeCell({ column, props, row, value }) {
37
+ const colorKey = props.badge.colorKey;
38
+ return /* @__PURE__ */ jsx(Badge, {
39
+ label: formatCell(value, column),
40
+ color: String(row[colorKey] ?? "")
41
+ });
42
+ }
43
+ function PlainTextCell({ column, props, row, value }) {
44
+ const dateProps = column.props?.date;
45
+ const href = resolveLink(column, row, value);
8
46
  const text = formatCell(value, column);
9
47
  const [copied, setCopied] = useState(false);
10
- const href = resolveLink(column, row, value);
11
48
  const content = href ? /* @__PURE__ */ jsx("a", {
12
49
  className: "underline underline-offset-2",
13
50
  href,
@@ -24,6 +61,11 @@ var TextCell = ({ column, props, row, value }) => {
24
61
  copyToClipboard(text);
25
62
  setCopied(true);
26
63
  }
64
+ if (dateProps && !href && !props.copyable && value !== null && value !== void 0) return /* @__PURE__ */ jsx(DateTime, {
65
+ value,
66
+ dateStyle: dateProps.dateStyle,
67
+ timeStyle: dateProps.timeStyle
68
+ });
27
69
  if (!props.copyable) return content;
28
70
  return /* @__PURE__ */ jsxs("span", {
29
71
  className: "inline-flex items-center gap-2",
@@ -42,7 +84,7 @@ var TextCell = ({ column, props, row, value }) => {
42
84
  }), copied ? "Copied" : "Copy"]
43
85
  })]
44
86
  });
45
- };
87
+ }
46
88
  //#endregion
47
89
  export { TextCell };
48
90
 
@@ -1 +1 @@
1
- {"version":3,"file":"text-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/text-cell.tsx"],"sourcesContent":["import { copyToClipboard } from \"@lattice-php/lattice/clipboard\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { useEffect, useState } from \"react\";\nimport { formatCell, resolveLink } from \"../../format\";\nimport type { ColumnCellComponent } from \"../../registry\";\n\nexport const TextCell: ColumnCellComponent<\"column.text\"> = ({ column, props, row, value }) => {\n const text = formatCell(value, column);\n const [copied, setCopied] = useState(false);\n const href = resolveLink(column, row, value);\n const content = href ? (\n <a\n className=\"underline underline-offset-2\"\n href={href}\n rel={props.link?.external ? \"noreferrer\" : undefined}\n target={props.link?.external ? \"_blank\" : undefined}\n >\n {text}\n </a>\n ) : (\n text\n );\n\n useEffect(() => {\n if (!copied) {\n return;\n }\n\n const timeout = window.setTimeout(() => setCopied(false), 1500);\n\n return () => window.clearTimeout(timeout);\n }, [copied]);\n\n function handleCopy(): void {\n void copyToClipboard(text);\n setCopied(true);\n }\n\n if (!props.copyable) {\n return content;\n }\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <span>{content}</span>\n <button\n type=\"button\"\n data-test={`copy-${column.key}`}\n className=\"inline-flex items-center gap-1 rounded-lt-sm border border-lt-border px-2 py-1 text-xs\"\n aria-label={`${copied ? \"Copied\" : \"Copy\"} ${column.label}`}\n onClick={handleCopy}\n >\n {copied ? (\n <Icon name=\"check\" className=\"size-lt-icon-xs\" />\n ) : (\n <Icon name=\"copy\" className=\"size-lt-icon-xs\" />\n )}\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </span>\n );\n};\n"],"mappings":";;;;;;AAMA,IAAa,YAAgD,EAAE,QAAQ,OAAO,KAAK,YAAY;CAC7F,MAAM,OAAO,WAAW,OAAO,MAAM;CACrC,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,OAAO,YAAY,QAAQ,KAAK,KAAK;CAC3C,MAAM,UAAU,OACd,oBAAC,KAAD;EACE,WAAU;EACJ;EACN,KAAK,MAAM,MAAM,WAAW,eAAe,KAAA;EAC3C,QAAQ,MAAM,MAAM,WAAW,WAAW,KAAA;YAEzC;CACA,CAAA,IAEH;CAGF,gBAAgB;EACd,IAAI,CAAC,QACH;EAGF,MAAM,UAAU,OAAO,iBAAiB,UAAU,KAAK,GAAG,IAAI;EAE9D,aAAa,OAAO,aAAa,OAAO;CAC1C,GAAG,CAAC,MAAM,CAAC;CAEX,SAAS,aAAmB;EAC1B,gBAAqB,IAAI;EACzB,UAAU,IAAI;CAChB;CAEA,IAAI,CAAC,MAAM,UACT,OAAO;CAGT,OACE,qBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,oBAAC,QAAD,EAAA,UAAO,QAAc,CAAA,GACrB,qBAAC,UAAD;GACE,MAAK;GACL,aAAW,QAAQ,OAAO;GAC1B,WAAU;GACV,cAAY,GAAG,SAAS,WAAW,OAAO,GAAG,OAAO;GACpD,SAAS;aALX,CAOG,SACC,oBAAC,MAAD;IAAM,MAAK;IAAQ,WAAU;GAAmB,CAAA,IAEhD,oBAAC,MAAD;IAAM,MAAK;IAAO,WAAU;GAAmB,CAAA,GAEhD,SAAS,WAAW,MACf;IACJ;;AAEV"}
1
+ {"version":3,"file":"text-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/text-cell.tsx"],"sourcesContent":["import { copyToClipboard } from \"@lattice-php/lattice/clipboard\";\nimport { DateTime } from \"@lattice-php/lattice/i18n\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { type ReactNode, useEffect, useState } from \"react\";\nimport { formatCell, resolveLink } from \"../../format\";\nimport type { ColumnCellArgs, ColumnCellComponent } from \"../../registry\";\nimport type { ColumnPropsOf } from \"../../types\";\n\ntype TextProps = ColumnCellArgs<\"column.text\">[\"props\"];\n\nexport const TextCell: ColumnCellComponent<\"column.text\"> = (args) => {\n if (args.props.multiple) {\n return <MultipleCell {...args} />;\n }\n\n if (args.props.badge) {\n return <SingleBadgeCell {...args} />;\n }\n\n return <PlainTextCell {...args} />;\n};\n\nfunction Badge({ label, color }: { label: string; color?: string | null }): ReactNode {\n if (label === \"\") {\n return null;\n }\n\n return <span className={cn(\"lt-cell-badge\", `lt-cell-tone-${color || \"gray\"}`)}>{label}</span>;\n}\n\nfunction MultipleCell({ column, props, value }: ColumnCellArgs<\"column.text\">): ReactNode {\n const items = Array.isArray(value) ? value : [];\n\n if (items.length === 0) {\n return null;\n }\n\n if (!props.badge) {\n return <span>{items.map((item) => formatCell(item, column)).join(\", \")}</span>;\n }\n\n return (\n <div className=\"flex flex-wrap gap-1\">\n {items.map((item, index) => {\n const chip = item as { value: unknown; color?: string };\n\n return <Badge key={index} label={formatCell(chip.value, column)} color={chip.color} />;\n })}\n </div>\n );\n}\n\nfunction SingleBadgeCell({ column, props, row, value }: ColumnCellArgs<\"column.text\">): ReactNode {\n const colorKey = (props.badge as NonNullable<TextProps[\"badge\"]>).colorKey;\n\n return <Badge label={formatCell(value, column)} color={String(row[colorKey] ?? \"\")} />;\n}\n\nfunction PlainTextCell({ column, props, row, value }: ColumnCellArgs<\"column.text\">): ReactNode {\n const dateProps = (column.props as ColumnPropsOf<\"column.text\"> | null)?.date;\n const href = resolveLink(column, row, value);\n const text = formatCell(value, column);\n const [copied, setCopied] = useState(false);\n\n const content = href ? (\n <a\n className=\"underline underline-offset-2\"\n href={href}\n rel={props.link?.external ? \"noreferrer\" : undefined}\n target={props.link?.external ? \"_blank\" : undefined}\n >\n {text}\n </a>\n ) : (\n text\n );\n\n useEffect(() => {\n if (!copied) {\n return;\n }\n\n const timeout = window.setTimeout(() => setCopied(false), 1500);\n\n return () => window.clearTimeout(timeout);\n }, [copied]);\n\n function handleCopy(): void {\n void copyToClipboard(text);\n setCopied(true);\n }\n\n if (dateProps && !href && !props.copyable && value !== null && value !== undefined) {\n return (\n <DateTime value={value} dateStyle={dateProps.dateStyle} timeStyle={dateProps.timeStyle} />\n );\n }\n\n if (!props.copyable) {\n return content;\n }\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <span>{content}</span>\n <button\n type=\"button\"\n data-test={`copy-${column.key}`}\n className=\"inline-flex items-center gap-1 rounded-lt-sm border border-lt-border px-2 py-1 text-xs\"\n aria-label={`${copied ? \"Copied\" : \"Copy\"} ${column.label}`}\n onClick={handleCopy}\n >\n {copied ? (\n <Icon name=\"check\" className=\"size-lt-icon-xs\" />\n ) : (\n <Icon name=\"copy\" className=\"size-lt-icon-xs\" />\n )}\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </span>\n );\n}\n"],"mappings":";;;;;;;;AAWA,IAAa,YAAgD,SAAS;CACpE,IAAI,KAAK,MAAM,UACb,OAAO,oBAAC,cAAD,EAAc,GAAI,KAAO,CAAA;CAGlC,IAAI,KAAK,MAAM,OACb,OAAO,oBAAC,iBAAD,EAAiB,GAAI,KAAO,CAAA;CAGrC,OAAO,oBAAC,eAAD,EAAe,GAAI,KAAO,CAAA;AACnC;AAEA,SAAS,MAAM,EAAE,OAAO,SAA8D;CACpF,IAAI,UAAU,IACZ,OAAO;CAGT,OAAO,oBAAC,QAAD;EAAM,WAAW,GAAG,iBAAiB,gBAAgB,SAAS,QAAQ;YAAI;CAAY,CAAA;AAC/F;AAEA,SAAS,aAAa,EAAE,QAAQ,OAAO,SAAmD;CACxF,MAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC;CAE9C,IAAI,MAAM,WAAW,GACnB,OAAO;CAGT,IAAI,CAAC,MAAM,OACT,OAAO,oBAAC,QAAD,EAAA,UAAO,MAAM,KAAK,SAAS,WAAW,MAAM,MAAM,CAAC,EAAE,KAAK,IAAI,EAAQ,CAAA;CAG/E,OACE,oBAAC,OAAD;EAAK,WAAU;YACZ,MAAM,KAAK,MAAM,UAAU;GAC1B,MAAM,OAAO;GAEb,OAAO,oBAAC,OAAD;IAAmB,OAAO,WAAW,KAAK,OAAO,MAAM;IAAG,OAAO,KAAK;GAAQ,GAAlE,KAAkE;EACvF,CAAC;CACE,CAAA;AAET;AAEA,SAAS,gBAAgB,EAAE,QAAQ,OAAO,KAAK,SAAmD;CAChG,MAAM,WAAY,MAAM,MAA0C;CAElE,OAAO,oBAAC,OAAD;EAAO,OAAO,WAAW,OAAO,MAAM;EAAG,OAAO,OAAO,IAAI,aAAa,EAAE;CAAI,CAAA;AACvF;AAEA,SAAS,cAAc,EAAE,QAAQ,OAAO,KAAK,SAAmD;CAC9F,MAAM,YAAa,OAAO,OAA+C;CACzE,MAAM,OAAO,YAAY,QAAQ,KAAK,KAAK;CAC3C,MAAM,OAAO,WAAW,OAAO,MAAM;CACrC,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAE1C,MAAM,UAAU,OACd,oBAAC,KAAD;EACE,WAAU;EACJ;EACN,KAAK,MAAM,MAAM,WAAW,eAAe,KAAA;EAC3C,QAAQ,MAAM,MAAM,WAAW,WAAW,KAAA;YAEzC;CACA,CAAA,IAEH;CAGF,gBAAgB;EACd,IAAI,CAAC,QACH;EAGF,MAAM,UAAU,OAAO,iBAAiB,UAAU,KAAK,GAAG,IAAI;EAE9D,aAAa,OAAO,aAAa,OAAO;CAC1C,GAAG,CAAC,MAAM,CAAC;CAEX,SAAS,aAAmB;EAC1B,gBAAqB,IAAI;EACzB,UAAU,IAAI;CAChB;CAEA,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,YAAY,UAAU,QAAQ,UAAU,KAAA,GACvE,OACE,oBAAC,UAAD;EAAiB;EAAO,WAAW,UAAU;EAAW,WAAW,UAAU;CAAY,CAAA;CAI7F,IAAI,CAAC,MAAM,UACT,OAAO;CAGT,OACE,qBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,oBAAC,QAAD,EAAA,UAAO,QAAc,CAAA,GACrB,qBAAC,UAAD;GACE,MAAK;GACL,aAAW,QAAQ,OAAO;GAC1B,WAAU;GACV,cAAY,GAAG,SAAS,WAAW,OAAO,GAAG,OAAO;GACpD,SAAS;aALX,CAOG,SACC,oBAAC,MAAD;IAAM,MAAK;IAAQ,WAAU;GAAmB,CAAA,IAEhD,oBAAC,MAAD;IAAM,MAAK;IAAO,WAAU;GAAmB,CAAA,GAEhD,SAAS,WAAW,MACf;IACJ;;AAEV"}
@@ -1,7 +1,7 @@
1
1
  import { cn } from "../../lib/utils.js";
2
- import { nodeIdentity } from "../../core/test-id.js";
3
2
  import { Icon } from "../../icons/sprite.js";
4
3
  import { useT } from "../../i18n/instance.js";
4
+ import { nodeIdentity } from "../../core/test-id.js";
5
5
  import { useColumnResizing } from "../../core/use-column-resizing.js";
6
6
  import { alignJustifyItems, alignText } from "../align.js";
7
7
  import { getBulkActions } from "../bulk.js";
@@ -1,4 +1,9 @@
1
1
  import { FilterData, Option } from '../types/generated';
2
+ /**
3
+ * Whether a single scalar filter member is absent — the atomic rule the
4
+ * filter-emptiness and query-serialization logic both build on.
5
+ */
6
+ export declare function isEmptyMember(value: unknown): value is null | undefined | "";
2
7
  /**
3
8
  * Whether a table-filter value should clear the filter rather than apply it —
4
9
  * an empty string, empty list, or an object whose every member is empty.
@@ -1,12 +1,19 @@
1
1
  //#region resources/js/table/filter-values.ts
2
2
  /**
3
+ * Whether a single scalar filter member is absent — the atomic rule the
4
+ * filter-emptiness and query-serialization logic both build on.
5
+ */
6
+ function isEmptyMember(value) {
7
+ return value == null || value === "";
8
+ }
9
+ /**
3
10
  * Whether a table-filter value should clear the filter rather than apply it —
4
11
  * an empty string, empty list, or an object whose every member is empty.
5
12
  */
6
13
  function isEmptyFilterValue(value) {
7
- if (value == null || value === "") return true;
14
+ if (isEmptyMember(value)) return true;
8
15
  if (Array.isArray(value)) return value.length === 0;
9
- if (typeof value === "object") return Object.values(value).every((member) => member == null || member === "");
16
+ if (typeof value === "object") return Object.values(value).every(isEmptyMember);
10
17
  return false;
11
18
  }
12
19
  function isActiveFilterValue(value) {
@@ -25,6 +32,6 @@ function filterOptions(filter) {
25
32
  return Array.isArray(value) ? value : [];
26
33
  }
27
34
  //#endregion
28
- export { filterOptions, isActiveFilterValue, isEmptyFilterValue, stringProp };
35
+ export { filterOptions, isActiveFilterValue, isEmptyFilterValue, isEmptyMember, stringProp };
29
36
 
30
37
  //# sourceMappingURL=filter-values.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filter-values.js","names":[],"sources":["../../resources/js/table/filter-values.ts"],"sourcesContent":["import type { FilterData, Option } from \"@lattice-php/lattice/types/generated\";\n\n/**\n * Whether a table-filter value should clear the filter rather than apply it —\n * an empty string, empty list, or an object whose every member is empty.\n */\nexport function isEmptyFilterValue(value: unknown): boolean {\n if (value == null || value === \"\") {\n return true;\n }\n\n if (Array.isArray(value)) {\n return value.length === 0;\n }\n\n if (typeof value === \"object\") {\n return Object.values(value).every((member) => member == null || member === \"\");\n }\n\n return false;\n}\n\nexport function isActiveFilterValue(value: unknown): boolean {\n return !isEmptyFilterValue(value);\n}\n\n/**\n * Read a string entry from a filter's loose `props` bag, falling back when the\n * key is absent or not a string.\n */\nexport function stringProp(filter: FilterData, key: string, fallback: string): string {\n const value = filter.props[key];\n\n return typeof value === \"string\" ? value : fallback;\n}\n\nexport function filterOptions(filter: FilterData): Option[] {\n const value = filter.props.options;\n\n return Array.isArray(value) ? (value as Option[]) : [];\n}\n"],"mappings":";;;;;AAMA,SAAgB,mBAAmB,OAAyB;CAC1D,IAAI,SAAS,QAAQ,UAAU,IAC7B,OAAO;CAGT,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,WAAW;CAG1B,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,OAAO,KAAK,EAAE,OAAO,WAAW,UAAU,QAAQ,WAAW,EAAE;CAG/E,OAAO;AACT;AAEA,SAAgB,oBAAoB,OAAyB;CAC3D,OAAO,CAAC,mBAAmB,KAAK;AAClC;;;;;AAMA,SAAgB,WAAW,QAAoB,KAAa,UAA0B;CACpF,MAAM,QAAQ,OAAO,MAAM;CAE3B,OAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAgB,cAAc,QAA8B;CAC1D,MAAM,QAAQ,OAAO,MAAM;CAE3B,OAAO,MAAM,QAAQ,KAAK,IAAK,QAAqB,CAAC;AACvD"}
1
+ {"version":3,"file":"filter-values.js","names":[],"sources":["../../resources/js/table/filter-values.ts"],"sourcesContent":["import type { FilterData, Option } from \"@lattice-php/lattice/types/generated\";\n\n/**\n * Whether a single scalar filter member is absent — the atomic rule the\n * filter-emptiness and query-serialization logic both build on.\n */\nexport function isEmptyMember(value: unknown): value is null | undefined | \"\" {\n return value == null || value === \"\";\n}\n\n/**\n * Whether a table-filter value should clear the filter rather than apply it —\n * an empty string, empty list, or an object whose every member is empty.\n */\nexport function isEmptyFilterValue(value: unknown): boolean {\n if (isEmptyMember(value)) {\n return true;\n }\n\n if (Array.isArray(value)) {\n return value.length === 0;\n }\n\n if (typeof value === \"object\") {\n return Object.values(value).every(isEmptyMember);\n }\n\n return false;\n}\n\nexport function isActiveFilterValue(value: unknown): boolean {\n return !isEmptyFilterValue(value);\n}\n\n/**\n * Read a string entry from a filter's loose `props` bag, falling back when the\n * key is absent or not a string.\n */\nexport function stringProp(filter: FilterData, key: string, fallback: string): string {\n const value = filter.props[key];\n\n return typeof value === \"string\" ? value : fallback;\n}\n\nexport function filterOptions(filter: FilterData): Option[] {\n const value = filter.props.options;\n\n return Array.isArray(value) ? (value as Option[]) : [];\n}\n"],"mappings":";;;;;AAMA,SAAgB,cAAc,OAAgD;CAC5E,OAAO,SAAS,QAAQ,UAAU;AACpC;;;;;AAMA,SAAgB,mBAAmB,OAAyB;CAC1D,IAAI,cAAc,KAAK,GACrB,OAAO;CAGT,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,WAAW;CAG1B,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,OAAO,KAAK,EAAE,MAAM,aAAa;CAGjD,OAAO;AACT;AAEA,SAAgB,oBAAoB,OAAyB;CAC3D,OAAO,CAAC,mBAAmB,KAAK;AAClC;;;;;AAMA,SAAgB,WAAW,QAAoB,KAAa,UAA0B;CACpF,MAAM,QAAQ,OAAO,MAAM;CAE3B,OAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAgB,cAAc,QAA8B;CAC1D,MAAM,QAAQ,OAAO,MAAM;CAE3B,OAAO,MAAM,QAAQ,KAAK,IAAK,QAAqB,CAAC;AACvD"}
@@ -1,3 +1,13 @@
1
1
  import { TableColumn, TableRow } from './types';
2
- export declare function formatCell(value: unknown, column?: TableColumn): string;
2
+ export type FormatOptions = {
3
+ locale?: string;
4
+ timeZone?: string;
5
+ };
6
+ export type DateConfig = {
7
+ dateStyle: string | null;
8
+ timeStyle: string | null;
9
+ };
10
+ export declare function formatCell(value: unknown, column?: TableColumn, options?: FormatOptions): string;
11
+ export declare function formatDateValue(value: unknown, date: DateConfig, options?: FormatOptions): string;
12
+ export declare function preciseDateTime(value: unknown, options?: FormatOptions): string;
3
13
  export declare function resolveLink(column: TableColumn, row: TableRow, value: unknown): string | null;
@@ -1,31 +1,33 @@
1
1
  //#region resources/js/table/format.ts
2
- function formatCell(value, column) {
2
+ function formatCell(value, column, options) {
3
3
  if (value === null || value === void 0) return "";
4
4
  const date = (column?.props)?.date;
5
- if (date) return formatDate(value, date.format ?? null);
5
+ if (date) return formatDateValue(value, date, options);
6
6
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
7
7
  return JSON.stringify(value);
8
8
  }
9
- function formatDate(value, format) {
9
+ function formatDateValue(value, date, options) {
10
+ const parsed = new Date(String(value));
11
+ if (Number.isNaN(parsed.getTime())) return String(value ?? "");
12
+ const intl = { timeZone: options?.timeZone };
13
+ if (date.dateStyle) intl.dateStyle = date.dateStyle;
14
+ if (date.timeStyle) intl.timeStyle = date.timeStyle;
15
+ return new Intl.DateTimeFormat(options?.locale, intl).format(parsed);
16
+ }
17
+ function preciseDateTime(value, options) {
10
18
  const date = new Date(String(value));
11
- if (Number.isNaN(date.getTime())) return formatCell(value);
12
- if (!format) return new Intl.DateTimeFormat(void 0, {
13
- dateStyle: "medium",
14
- timeStyle: "short"
19
+ if (Number.isNaN(date.getTime())) return "";
20
+ const formatted = new Intl.DateTimeFormat(options?.locale, {
21
+ year: "numeric",
22
+ month: "short",
23
+ day: "numeric",
24
+ hour: "2-digit",
25
+ minute: "2-digit",
26
+ second: "2-digit",
27
+ timeZone: options?.timeZone,
28
+ timeZoneName: "short"
15
29
  }).format(date);
16
- const replacements = {
17
- Y: String(date.getFullYear()),
18
- y: String(date.getFullYear()).slice(-2),
19
- m: String(date.getMonth() + 1).padStart(2, "0"),
20
- n: String(date.getMonth() + 1),
21
- d: String(date.getDate()).padStart(2, "0"),
22
- j: String(date.getDate()),
23
- H: String(date.getHours()).padStart(2, "0"),
24
- G: String(date.getHours()),
25
- i: String(date.getMinutes()).padStart(2, "0"),
26
- s: String(date.getSeconds()).padStart(2, "0")
27
- };
28
- return format.replace(/[YymndjHGis]/g, (token) => replacements[token] ?? token);
30
+ return options?.timeZone ? `${formatted} (${options.timeZone})` : formatted;
29
31
  }
30
32
  function resolveLink(column, row, value) {
31
33
  const link = column.props?.link;
@@ -38,6 +40,6 @@ function resolveLink(column, row, value) {
38
40
  });
39
41
  }
40
42
  //#endregion
41
- export { formatCell, resolveLink };
43
+ export { formatCell, formatDateValue, preciseDateTime, resolveLink };
42
44
 
43
45
  //# sourceMappingURL=format.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","names":[],"sources":["../../resources/js/table/format.ts"],"sourcesContent":["import type { ColumnPropsOf, TableColumn, TableRow } from \"./types\";\n\nexport function formatCell(value: unknown, column?: TableColumn): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n const date = (column?.props as ColumnPropsOf<\"column.text\"> | null)?.date;\n\n if (date) {\n return formatDate(value, date.format ?? null);\n }\n\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n\n return JSON.stringify(value);\n}\n\nfunction formatDate(value: unknown, format: string | null): string {\n const date = new Date(String(value));\n\n if (Number.isNaN(date.getTime())) {\n return formatCell(value);\n }\n\n if (!format) {\n return new Intl.DateTimeFormat(undefined, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n }).format(date);\n }\n\n const replacements: Record<string, string> = {\n Y: String(date.getFullYear()),\n y: String(date.getFullYear()).slice(-2),\n m: String(date.getMonth() + 1).padStart(2, \"0\"),\n n: String(date.getMonth() + 1),\n d: String(date.getDate()).padStart(2, \"0\"),\n j: String(date.getDate()),\n H: String(date.getHours()).padStart(2, \"0\"),\n G: String(date.getHours()),\n i: String(date.getMinutes()).padStart(2, \"0\"),\n s: String(date.getSeconds()).padStart(2, \"0\"),\n };\n\n return format.replace(/[YymndjHGis]/g, (token) => replacements[token] ?? token);\n}\n\nexport function resolveLink(column: TableColumn, row: TableRow, value: unknown): string | null {\n const link = (column.props as ColumnPropsOf<\"column.text\"> | null)?.link;\n\n if (!link) {\n return null;\n }\n\n const href = link.href ?? String(value ?? \"\");\n\n if (href === \"\") {\n return null;\n }\n\n return href.replace(/\\{([^}]+)\\}/g, (_, key: string) => {\n if (key === \"value\") {\n return encodeURIComponent(String(value ?? \"\"));\n }\n\n return encodeURIComponent(String(row[key] ?? \"\"));\n });\n}\n"],"mappings":";AAEA,SAAgB,WAAW,OAAgB,QAA8B;CACvE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;CAGT,MAAM,QAAQ,QAAQ,QAA+C;CAErE,IAAI,MACF,OAAO,WAAW,OAAO,KAAK,UAAU,IAAI;CAG9C,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAC7E,OAAO,OAAO,KAAK;CAGrB,OAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,SAAS,WAAW,OAAgB,QAA+B;CACjE,MAAM,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC;CAEnC,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAC7B,OAAO,WAAW,KAAK;CAGzB,IAAI,CAAC,QACH,OAAO,IAAI,KAAK,eAAe,KAAA,GAAW;EACxC,WAAW;EACX,WAAW;CACb,CAAC,EAAE,OAAO,IAAI;CAGhB,MAAM,eAAuC;EAC3C,GAAG,OAAO,KAAK,YAAY,CAAC;EAC5B,GAAG,OAAO,KAAK,YAAY,CAAC,EAAE,MAAM,EAAE;EACtC,GAAG,OAAO,KAAK,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;EAC9C,GAAG,OAAO,KAAK,SAAS,IAAI,CAAC;EAC7B,GAAG,OAAO,KAAK,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;EACzC,GAAG,OAAO,KAAK,QAAQ,CAAC;EACxB,GAAG,OAAO,KAAK,SAAS,CAAC,EAAE,SAAS,GAAG,GAAG;EAC1C,GAAG,OAAO,KAAK,SAAS,CAAC;EACzB,GAAG,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;EAC5C,GAAG,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;CAC9C;CAEA,OAAO,OAAO,QAAQ,kBAAkB,UAAU,aAAa,UAAU,KAAK;AAChF;AAEA,SAAgB,YAAY,QAAqB,KAAe,OAA+B;CAC7F,MAAM,OAAQ,OAAO,OAA+C;CAEpE,IAAI,CAAC,MACH,OAAO;CAGT,MAAM,OAAO,KAAK,QAAQ,OAAO,SAAS,EAAE;CAE5C,IAAI,SAAS,IACX,OAAO;CAGT,OAAO,KAAK,QAAQ,iBAAiB,GAAG,QAAgB;EACtD,IAAI,QAAQ,SACV,OAAO,mBAAmB,OAAO,SAAS,EAAE,CAAC;EAG/C,OAAO,mBAAmB,OAAO,IAAI,QAAQ,EAAE,CAAC;CAClD,CAAC;AACH"}
1
+ {"version":3,"file":"format.js","names":[],"sources":["../../resources/js/table/format.ts"],"sourcesContent":["import type { ColumnPropsOf, TableColumn, TableRow } from \"./types\";\n\nexport type FormatOptions = {\n locale?: string;\n timeZone?: string;\n};\n\nexport type DateConfig = { dateStyle: string | null; timeStyle: string | null };\n\nexport function formatCell(value: unknown, column?: TableColumn, options?: FormatOptions): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n const date = (column?.props as ColumnPropsOf<\"column.text\"> | null)?.date;\n\n if (date) {\n return formatDateValue(value, date, options);\n }\n\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n\n return JSON.stringify(value);\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 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\nexport function resolveLink(column: TableColumn, row: TableRow, value: unknown): string | null {\n const link = (column.props as ColumnPropsOf<\"column.text\"> | null)?.link;\n\n if (!link) {\n return null;\n }\n\n const href = link.href ?? String(value ?? \"\");\n\n if (href === \"\") {\n return null;\n }\n\n return href.replace(/\\{([^}]+)\\}/g, (_, key: string) => {\n if (key === \"value\") {\n return encodeURIComponent(String(value ?? \"\"));\n }\n\n return encodeURIComponent(String(row[key] ?? \"\"));\n });\n}\n"],"mappings":";AASA,SAAgB,WAAW,OAAgB,QAAsB,SAAiC;CAChG,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;CAGT,MAAM,QAAQ,QAAQ,QAA+C;CAErE,IAAI,MACF,OAAO,gBAAgB,OAAO,MAAM,OAAO;CAG7C,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAC7E,OAAO,OAAO,KAAK;CAGrB,OAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,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,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;AAEA,SAAgB,YAAY,QAAqB,KAAe,OAA+B;CAC7F,MAAM,OAAQ,OAAO,OAA+C;CAEpE,IAAI,CAAC,MACH,OAAO;CAGT,MAAM,OAAO,KAAK,QAAQ,OAAO,SAAS,EAAE;CAE5C,IAAI,SAAS,IACX,OAAO;CAGT,OAAO,KAAK,QAAQ,iBAAiB,GAAG,QAAgB;EACtD,IAAI,QAAQ,SACV,OAAO,mBAAmB,OAAO,SAAS,EAAE,CAAC;EAG/C,OAAO,mBAAmB,OAAO,IAAI,QAAQ,EAAE,CAAC;CAClD,CAAC;AACH"}
@@ -1,5 +1,6 @@
1
1
  import { translate } from "../i18n/instance.js";
2
2
  import "../core/column-sizing.js";
3
+ import { isEmptyMember } from "./filter-values.js";
3
4
  //#region resources/js/table/query.ts
4
5
  function getColumnSort(state, column) {
5
6
  return state.sorts.find((currentSort) => currentSort.key === column.key);
@@ -30,7 +31,7 @@ function appendTableFilters(url, tableFilters) {
30
31
  continue;
31
32
  }
32
33
  if (Array.isArray(value)) {
33
- for (const item of value) if (item != null && item !== "") url.searchParams.append(`tf[${key}][]`, String(item));
34
+ for (const item of value) if (!isEmptyMember(item)) url.searchParams.append(`tf[${key}][]`, String(item));
34
35
  continue;
35
36
  }
36
37
  if (typeof value === "object" && value !== null) for (const [subKey, subValue] of Object.entries(value)) url.searchParams.set(`tf[${key}][${subKey}]`, String(subValue));
@@ -45,14 +46,14 @@ function getTableFilterParams(tableFilters) {
45
46
  return params;
46
47
  }
47
48
  function normalizeTableFilterValue(value) {
48
- if (value == null || value === "") return;
49
+ if (isEmptyMember(value)) return;
49
50
  if (typeof value === "string") return value;
50
51
  if (Array.isArray(value)) {
51
- const items = value.filter((item) => item != null && item !== "").map(String);
52
+ const items = value.filter((item) => !isEmptyMember(item)).map(String);
52
53
  return items.length > 0 ? items : void 0;
53
54
  }
54
55
  if (typeof value === "object") {
55
- const entries = Object.entries(value).filter(([, subValue]) => subValue != null && subValue !== "").map(([subKey, subValue]) => [subKey, String(subValue)]);
56
+ const entries = Object.entries(value).filter(([, subValue]) => !isEmptyMember(subValue)).map(([subKey, subValue]) => [subKey, String(subValue)]);
56
57
  return entries.length > 0 ? Object.fromEntries(entries) : void 0;
57
58
  }
58
59
  return String(value);