@lattice-php/lattice 0.3.0 → 0.4.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 (134) hide show
  1. package/dist/chat/components/chat-box.js +1 -1
  2. package/dist/chat/components/chat-box.js.map +1 -1
  3. package/dist/chat/components/message.js +1 -1
  4. package/dist/chat/components/message.js.map +1 -1
  5. package/dist/chat/transport.d.ts +2 -0
  6. package/dist/chat/transport.js +23 -3
  7. package/dist/chat/transport.js.map +1 -1
  8. package/dist/core/api.d.ts +14 -7
  9. package/dist/core/api.js +77 -1
  10. package/dist/core/api.js.map +1 -1
  11. package/dist/core/components/badge.js +3 -3
  12. package/dist/core/components/badge.js.map +1 -1
  13. package/dist/core/components/button.js +15 -15
  14. package/dist/core/components/button.js.map +1 -1
  15. package/dist/core/components/card.js +1 -1
  16. package/dist/core/components/card.js.map +1 -1
  17. package/dist/core/components/chart.js +1 -1
  18. package/dist/core/components/chart.js.map +1 -1
  19. package/dist/core/components/checkbox.js +1 -1
  20. package/dist/core/components/checkbox.js.map +1 -1
  21. package/dist/core/components/control.js +4 -4
  22. package/dist/core/components/control.js.map +1 -1
  23. package/dist/core/components/dialog.d.ts +1 -1
  24. package/dist/core/components/dialog.js +2 -1
  25. package/dist/core/components/dialog.js.map +1 -1
  26. package/dist/core/components/floating-panel.js +2 -2
  27. package/dist/core/components/floating-panel.js.map +1 -1
  28. package/dist/core/components/heading.js +1 -1
  29. package/dist/core/components/heading.js.map +1 -1
  30. package/dist/core/components/popover.d.ts +1 -1
  31. package/dist/core/components/popover.js +1 -1
  32. package/dist/core/components/popover.js.map +1 -1
  33. package/dist/core/components/section.js +2 -2
  34. package/dist/core/components/section.js.map +1 -1
  35. package/dist/core/components/segmented-pills.js +1 -1
  36. package/dist/core/components/segmented-pills.js.map +1 -1
  37. package/dist/core/components/stack.js +7 -1
  38. package/dist/core/components/stack.js.map +1 -1
  39. package/dist/core/components/tabs.js +1 -1
  40. package/dist/core/components/tabs.js.map +1 -1
  41. package/dist/core/types.d.ts +3 -1
  42. package/dist/form/components/base/affix-group.d.ts +17 -0
  43. package/dist/form/components/base/affix-group.js +49 -0
  44. package/dist/form/components/base/affix-group.js.map +1 -0
  45. package/dist/form/components/base/label.js +1 -1
  46. package/dist/form/components/base/label.js.map +1 -1
  47. package/dist/form/components/base/submit-button.js +1 -1
  48. package/dist/form/components/base/submit-button.js.map +1 -1
  49. package/dist/form/components/base/textarea.js +1 -1
  50. package/dist/form/components/base/textarea.js.map +1 -1
  51. package/dist/form/components/fields/file-upload.js +34 -2
  52. package/dist/form/components/fields/file-upload.js.map +1 -1
  53. package/dist/form/components/fields/number-input.js +21 -15
  54. package/dist/form/components/fields/number-input.js.map +1 -1
  55. package/dist/form/components/fields/password-input.js +21 -15
  56. package/dist/form/components/fields/password-input.js.map +1 -1
  57. package/dist/form/components/fields/rich-editor.js +2 -2
  58. package/dist/form/components/fields/rich-editor.js.map +1 -1
  59. package/dist/form/components/fields/text-input.js +19 -13
  60. package/dist/form/components/fields/text-input.js.map +1 -1
  61. package/dist/form/components/form.js +1 -1
  62. package/dist/form/components/form.js.map +1 -1
  63. package/dist/form/skeleton.js +1 -1
  64. package/dist/form/skeleton.js.map +1 -1
  65. package/dist/i18n/translatable.d.ts +4 -0
  66. package/dist/i18n/translatable.js +24 -0
  67. package/dist/i18n/translatable.js.map +1 -0
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.js +2 -1
  70. package/dist/lattice.css +99 -6
  71. package/dist/layout/callouts.js +1 -1
  72. package/dist/layout/callouts.js.map +1 -1
  73. package/dist/layout/components.js +3 -1
  74. package/dist/layout/components.js.map +1 -1
  75. package/dist/layout/dropdown.js +2 -1
  76. package/dist/layout/dropdown.js.map +1 -1
  77. package/dist/layout/menu-item.js +31 -10
  78. package/dist/layout/menu-item.js.map +1 -1
  79. package/dist/layout/sidebar.js +1 -1
  80. package/dist/layout/sidebar.js.map +1 -1
  81. package/dist/layout/topbar.d.ts +3 -0
  82. package/dist/layout/topbar.js +15 -0
  83. package/dist/layout/topbar.js.map +1 -0
  84. package/dist/page.js +15 -10
  85. package/dist/page.js.map +1 -1
  86. package/dist/realtime/build-effects.d.ts +4 -0
  87. package/dist/realtime/build-effects.js +23 -0
  88. package/dist/realtime/build-effects.js.map +1 -0
  89. package/dist/realtime/listeners.d.ts +8 -0
  90. package/dist/realtime/listeners.js +31 -0
  91. package/dist/realtime/listeners.js.map +1 -0
  92. package/dist/realtime/subscriptions.d.ts +4 -0
  93. package/dist/realtime/subscriptions.js +45 -0
  94. package/dist/realtime/subscriptions.js.map +1 -0
  95. package/dist/registry.js +2 -1
  96. package/dist/registry.js.map +1 -1
  97. package/dist/remote/components/chat-box.d.ts +3 -0
  98. package/dist/remote/components/chat-box.js +65 -0
  99. package/dist/remote/components/chat-box.js.map +1 -0
  100. package/dist/remote/components/data-list.d.ts +10 -0
  101. package/dist/remote/components/data-list.js +106 -0
  102. package/dist/remote/components/data-list.js.map +1 -0
  103. package/dist/remote/index.d.ts +1 -0
  104. package/dist/remote/index.js +15 -0
  105. package/dist/remote/index.js.map +1 -0
  106. package/dist/table/components/cells/text-cell.js +1 -1
  107. package/dist/table/components/cells/text-cell.js.map +1 -1
  108. package/dist/table/components/column-filter-control.d.ts +2 -1
  109. package/dist/table/components/column-filter-control.js +28 -19
  110. package/dist/table/components/column-filter-control.js.map +1 -1
  111. package/dist/table/components/column-header.js +2 -2
  112. package/dist/table/components/column-header.js.map +1 -1
  113. package/dist/table/components/filter-bar.d.ts +7 -6
  114. package/dist/table/components/filter-bar.js +51 -28
  115. package/dist/table/components/filter-bar.js.map +1 -1
  116. package/dist/table/components/filter-controls.js +2 -2
  117. package/dist/table/components/filter-controls.js.map +1 -1
  118. package/dist/table/components/filter-stack-bar.js +4 -3
  119. package/dist/table/components/filter-stack-bar.js.map +1 -1
  120. package/dist/table/components/filter-value-input.js +1 -1
  121. package/dist/table/components/filter-value-input.js.map +1 -1
  122. package/dist/table/components/sort-bar.js +1 -1
  123. package/dist/table/components/sort-bar.js.map +1 -1
  124. package/dist/table/components/table.js +25 -16
  125. package/dist/table/components/table.js.map +1 -1
  126. package/dist/table/use-table.d.ts +1 -0
  127. package/dist/table/use-table.js +4 -0
  128. package/dist/table/use-table.js.map +1 -1
  129. package/dist/toast/toast.js +5 -2
  130. package/dist/toast/toast.js.map +1 -1
  131. package/dist/toast/toaster.js +4 -3
  132. package/dist/toast/toaster.js.map +1 -1
  133. package/dist/types/generated.d.ts +88 -4
  134. package/package.json +20 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-table.js","names":[],"sources":["../../resources/js/table/use-table.ts"],"sourcesContent":["import { apiFetch, apiJson } from \"@lattice-php/lattice/core/api\";\nimport { LATTICE_EVENT, type ReloadComponentEvent } from \"@lattice-php/lattice/events/event-names\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { Option } from \"@lattice-php/lattice/types/generated\";\nimport { isEmptyFilterValue } from \"./filter-values\";\nimport { getColumns, getPagination, getRows, getState } from \"./payload\";\nimport { buildEndpoint, nextSort } from \"./query\";\nimport type {\n FilterClause,\n TableColumn,\n TableNode,\n TableResponse,\n TableSort,\n TableState,\n} from \"./types\";\n\nexport function useTable(node: TableNode) {\n const columns = useMemo(() => getColumns(node.props?.columns), [node.props?.columns]);\n const endpoint = typeof node.props?.endpoint === \"string\" ? node.props.endpoint : null;\n const componentRef = typeof node.props?.ref === \"string\" ? node.props.ref : \"\";\n const isLazy = node.props?.lazy === true;\n const initialState = useMemo(() => getState(node.props?.state), [node.props?.state]);\n const initialRows = useMemo(() => getRows(node.props?.data), [node.props?.data]);\n const initialPagination = useMemo(\n () => getPagination(node.props?.pagination),\n [node.props?.pagination],\n );\n const [rows, setRows] = useState(initialRows);\n const [pagination, setPagination] = useState(initialPagination);\n const [state, setState] = useState(initialState);\n const [processing, setProcessing] = useState(isLazy);\n const [hasLoaded, setHasLoaded] = useState(!isLazy);\n const infiniteLoaderRef = useRef<HTMLDivElement | null>(null);\n const currentPage = pagination.currentPage ?? state.page;\n const isInfinite = (pagination.mode ?? \"table\") === \"infinite\";\n\n const load = useCallback(\n async (nextState: TableState, append = false): Promise<void> => {\n if (!endpoint) {\n return;\n }\n\n setProcessing(true);\n\n try {\n const result = await apiJson<TableResponse>(buildEndpoint(endpoint, nextState), {\n ref: componentRef,\n });\n const resultState = getState(result.state);\n const resultRows = getRows(result.data);\n\n setRows((currentRows) => (append ? [...currentRows, ...resultRows] : resultRows));\n setPagination(getPagination(result.pagination));\n setState(resultState);\n setHasLoaded(true);\n } finally {\n setProcessing(false);\n }\n },\n [endpoint, componentRef],\n );\n\n function sort(column: TableColumn): void {\n void load({\n ...state,\n page: 1,\n sorts: nextSort(state.sorts, column),\n });\n }\n\n function clearSort(sort: TableSort): void {\n void load({\n ...state,\n page: 1,\n sorts: state.sorts.filter((currentSort) => currentSort.key !== sort.key),\n });\n }\n\n function applyFilters(next: FilterClause[]): void {\n const nextState = { ...state, filters: next, page: 1 };\n\n setState(nextState);\n void load(nextState);\n }\n\n function addFilter(clause: FilterClause): void {\n applyFilters([...state.filters, clause]);\n }\n\n function updateFilter(index: number, clause: FilterClause): void {\n applyFilters(state.filters.map((current, position) => (position === index ? clause : current)));\n }\n\n function removeFilter(index: number): void {\n applyFilters(state.filters.filter((_, current) => current !== index));\n }\n\n function setTableFilter(key: string, value: unknown): void {\n const next = { ...state.tableFilters };\n\n if (isEmptyFilterValue(value)) {\n delete next[key];\n } else {\n next[key] = value;\n }\n\n const nextState = { ...state, tableFilters: next, page: 1 };\n\n setState(nextState);\n void load(nextState);\n }\n\n function resetFilters(): void {\n const nextState = { ...state, filters: [], tableFilters: {}, page: 1 };\n\n setState(nextState);\n void load(nextState);\n }\n\n const searchFilterOptions = useCallback(\n async (filterKey: string, query: string): Promise<Option[]> => {\n if (!endpoint) {\n return [];\n }\n\n const url = new URL(endpoint, window.location.origin);\n url.searchParams.set(\"_search\", filterKey);\n url.searchParams.set(\"q\", query);\n\n const response = await apiFetch(`${url.pathname}${url.search}`, {\n ref: componentRef,\n throwOnError: false,\n });\n\n if (!response.ok) {\n return [];\n }\n\n const result = (await response.json()) as { options?: Option[] };\n\n return result.options ?? [];\n },\n [endpoint, componentRef],\n );\n\n function goToPage(page: number): void {\n void load({\n ...state,\n page,\n });\n }\n\n const loadMore = useCallback((): void => {\n if (processing || !pagination.hasMore) {\n return;\n }\n\n void load(\n {\n ...state,\n page: pagination.nextPage ?? currentPage + 1,\n },\n true,\n );\n }, [currentPage, load, pagination.hasMore, pagination.nextPage, processing, state]);\n\n useEffect(() => {\n setRows(initialRows);\n setPagination(initialPagination);\n setState(initialState);\n setProcessing(isLazy);\n setHasLoaded(!isLazy);\n }, [initialRows, initialPagination, initialState, isLazy]);\n\n useEffect(() => {\n if (!isLazy || hasLoaded) {\n return;\n }\n\n void load(state);\n }, [hasLoaded, isLazy, load, state]);\n\n useEffect(() => {\n function reload(event: Event): void {\n const detail = (event as ReloadComponentEvent).detail;\n\n if (detail?.component !== node.id) {\n return;\n }\n\n void load(state);\n }\n\n window.addEventListener(LATTICE_EVENT.reloadComponent, reload);\n\n return () => window.removeEventListener(LATTICE_EVENT.reloadComponent, reload);\n }, [load, node.id, state]);\n\n useEffect(() => {\n if (\n !isInfinite ||\n !pagination.hasMore ||\n processing ||\n !infiniteLoaderRef.current ||\n typeof IntersectionObserver === \"undefined\"\n ) {\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore();\n }\n },\n {\n rootMargin: \"240px\",\n },\n );\n\n observer.observe(infiniteLoaderRef.current);\n\n return () => observer.disconnect();\n }, [isInfinite, loadMore, pagination.hasMore, processing]);\n\n return {\n columns,\n rows,\n pagination,\n state,\n filters: state.filters,\n tableFilters: state.tableFilters,\n addFilter,\n updateFilter,\n removeFilter,\n setTableFilter,\n resetFilters,\n searchFilterOptions,\n processing,\n hasLoaded,\n infiniteLoaderRef,\n sort,\n clearSort,\n goToPage,\n loadMore,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-table.js","names":[],"sources":["../../resources/js/table/use-table.ts"],"sourcesContent":["import { apiFetch, apiJson } from \"@lattice-php/lattice/core/api\";\nimport { LATTICE_EVENT, type ReloadComponentEvent } from \"@lattice-php/lattice/events/event-names\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { Option } from \"@lattice-php/lattice/types/generated\";\nimport { isEmptyFilterValue } from \"./filter-values\";\nimport { getColumns, getPagination, getRows, getState } from \"./payload\";\nimport { buildEndpoint, nextSort } from \"./query\";\nimport type {\n FilterClause,\n TableColumn,\n TableNode,\n TableResponse,\n TableSort,\n TableState,\n} from \"./types\";\n\nexport function useTable(node: TableNode) {\n const columns = useMemo(() => getColumns(node.props?.columns), [node.props?.columns]);\n const endpoint = typeof node.props?.endpoint === \"string\" ? node.props.endpoint : null;\n const componentRef = typeof node.props?.ref === \"string\" ? node.props.ref : \"\";\n const isLazy = node.props?.lazy === true;\n const initialState = useMemo(() => getState(node.props?.state), [node.props?.state]);\n const initialRows = useMemo(() => getRows(node.props?.data), [node.props?.data]);\n const initialPagination = useMemo(\n () => getPagination(node.props?.pagination),\n [node.props?.pagination],\n );\n const [rows, setRows] = useState(initialRows);\n const [pagination, setPagination] = useState(initialPagination);\n const [state, setState] = useState(initialState);\n const [processing, setProcessing] = useState(isLazy);\n const [hasLoaded, setHasLoaded] = useState(!isLazy);\n const infiniteLoaderRef = useRef<HTMLDivElement | null>(null);\n const currentPage = pagination.currentPage ?? state.page;\n const isInfinite = (pagination.mode ?? \"table\") === \"infinite\";\n\n const load = useCallback(\n async (nextState: TableState, append = false): Promise<void> => {\n if (!endpoint) {\n return;\n }\n\n setProcessing(true);\n\n try {\n const result = await apiJson<TableResponse>(buildEndpoint(endpoint, nextState), {\n ref: componentRef,\n });\n const resultState = getState(result.state);\n const resultRows = getRows(result.data);\n\n setRows((currentRows) => (append ? [...currentRows, ...resultRows] : resultRows));\n setPagination(getPagination(result.pagination));\n setState(resultState);\n setHasLoaded(true);\n } finally {\n setProcessing(false);\n }\n },\n [endpoint, componentRef],\n );\n\n function sort(column: TableColumn): void {\n void load({\n ...state,\n page: 1,\n sorts: nextSort(state.sorts, column),\n });\n }\n\n function clearSort(sort: TableSort): void {\n void load({\n ...state,\n page: 1,\n sorts: state.sorts.filter((currentSort) => currentSort.key !== sort.key),\n });\n }\n\n function applyFilters(next: FilterClause[]): void {\n const nextState = { ...state, filters: next, page: 1 };\n\n setState(nextState);\n void load(nextState);\n }\n\n function addFilter(clause: FilterClause): void {\n applyFilters([...state.filters, clause]);\n }\n\n function updateFilter(index: number, clause: FilterClause): void {\n applyFilters(state.filters.map((current, position) => (position === index ? clause : current)));\n }\n\n function removeFilter(index: number): void {\n applyFilters(state.filters.filter((_, current) => current !== index));\n }\n\n function replaceColumnFilters(field: string, clauses: FilterClause[]): void {\n applyFilters([...state.filters.filter((clause) => clause.field !== field), ...clauses]);\n }\n\n function setTableFilter(key: string, value: unknown): void {\n const next = { ...state.tableFilters };\n\n if (isEmptyFilterValue(value)) {\n delete next[key];\n } else {\n next[key] = value;\n }\n\n const nextState = { ...state, tableFilters: next, page: 1 };\n\n setState(nextState);\n void load(nextState);\n }\n\n function resetFilters(): void {\n const nextState = { ...state, filters: [], tableFilters: {}, page: 1 };\n\n setState(nextState);\n void load(nextState);\n }\n\n const searchFilterOptions = useCallback(\n async (filterKey: string, query: string): Promise<Option[]> => {\n if (!endpoint) {\n return [];\n }\n\n const url = new URL(endpoint, window.location.origin);\n url.searchParams.set(\"_search\", filterKey);\n url.searchParams.set(\"q\", query);\n\n const response = await apiFetch(`${url.pathname}${url.search}`, {\n ref: componentRef,\n throwOnError: false,\n });\n\n if (!response.ok) {\n return [];\n }\n\n const result = (await response.json()) as { options?: Option[] };\n\n return result.options ?? [];\n },\n [endpoint, componentRef],\n );\n\n function goToPage(page: number): void {\n void load({\n ...state,\n page,\n });\n }\n\n const loadMore = useCallback((): void => {\n if (processing || !pagination.hasMore) {\n return;\n }\n\n void load(\n {\n ...state,\n page: pagination.nextPage ?? currentPage + 1,\n },\n true,\n );\n }, [currentPage, load, pagination.hasMore, pagination.nextPage, processing, state]);\n\n useEffect(() => {\n setRows(initialRows);\n setPagination(initialPagination);\n setState(initialState);\n setProcessing(isLazy);\n setHasLoaded(!isLazy);\n }, [initialRows, initialPagination, initialState, isLazy]);\n\n useEffect(() => {\n if (!isLazy || hasLoaded) {\n return;\n }\n\n void load(state);\n }, [hasLoaded, isLazy, load, state]);\n\n useEffect(() => {\n function reload(event: Event): void {\n const detail = (event as ReloadComponentEvent).detail;\n\n if (detail?.component !== node.id) {\n return;\n }\n\n void load(state);\n }\n\n window.addEventListener(LATTICE_EVENT.reloadComponent, reload);\n\n return () => window.removeEventListener(LATTICE_EVENT.reloadComponent, reload);\n }, [load, node.id, state]);\n\n useEffect(() => {\n if (\n !isInfinite ||\n !pagination.hasMore ||\n processing ||\n !infiniteLoaderRef.current ||\n typeof IntersectionObserver === \"undefined\"\n ) {\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore();\n }\n },\n {\n rootMargin: \"240px\",\n },\n );\n\n observer.observe(infiniteLoaderRef.current);\n\n return () => observer.disconnect();\n }, [isInfinite, loadMore, pagination.hasMore, processing]);\n\n return {\n columns,\n rows,\n pagination,\n state,\n filters: state.filters,\n tableFilters: state.tableFilters,\n addFilter,\n updateFilter,\n removeFilter,\n replaceColumnFilters,\n setTableFilter,\n resetFilters,\n searchFilterOptions,\n processing,\n hasLoaded,\n infiniteLoaderRef,\n sort,\n clearSort,\n goToPage,\n loadMore,\n };\n}\n"],"mappings":";;;;;;;AAgBA,SAAgB,SAAS,MAAiB;CACxC,MAAM,UAAU,cAAc,WAAW,KAAK,OAAO,OAAO,GAAG,CAAC,KAAK,OAAO,OAAO,CAAC;CACpF,MAAM,WAAW,OAAO,KAAK,OAAO,aAAa,WAAW,KAAK,MAAM,WAAW;CAClF,MAAM,eAAe,OAAO,KAAK,OAAO,QAAQ,WAAW,KAAK,MAAM,MAAM;CAC5E,MAAM,SAAS,KAAK,OAAO,SAAS;CACpC,MAAM,eAAe,cAAc,SAAS,KAAK,OAAO,KAAK,GAAG,CAAC,KAAK,OAAO,KAAK,CAAC;CACnF,MAAM,cAAc,cAAc,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,KAAK,OAAO,IAAI,CAAC;CAC/E,MAAM,oBAAoB,cAClB,cAAc,KAAK,OAAO,UAAU,GAC1C,CAAC,KAAK,OAAO,UAAU,CACzB;CACA,MAAM,CAAC,MAAM,WAAW,SAAS,WAAW;CAC5C,MAAM,CAAC,YAAY,iBAAiB,SAAS,iBAAiB;CAC9D,MAAM,CAAC,OAAO,YAAY,SAAS,YAAY;CAC/C,MAAM,CAAC,YAAY,iBAAiB,SAAS,MAAM;CACnD,MAAM,CAAC,WAAW,gBAAgB,SAAS,CAAC,MAAM;CAClD,MAAM,oBAAoB,OAA8B,IAAI;CAC5D,MAAM,cAAc,WAAW,eAAe,MAAM;CACpD,MAAM,cAAc,WAAW,QAAQ,aAAa;CAEpD,MAAM,OAAO,YACX,OAAO,WAAuB,SAAS,UAAyB;EAC9D,IAAI,CAAC,UACH;EAGF,cAAc,IAAI;EAElB,IAAI;GACF,MAAM,SAAS,MAAM,QAAuB,cAAc,UAAU,SAAS,GAAG,EAC9E,KAAK,aACP,CAAC;GACD,MAAM,cAAc,SAAS,OAAO,KAAK;GACzC,MAAM,aAAa,QAAQ,OAAO,IAAI;GAEtC,SAAS,gBAAiB,SAAS,CAAC,GAAG,aAAa,GAAG,UAAU,IAAI,UAAW;GAChF,cAAc,cAAc,OAAO,UAAU,CAAC;GAC9C,SAAS,WAAW;GACpB,aAAa,IAAI;EACnB,UAAU;GACR,cAAc,KAAK;EACrB;CACF,GACA,CAAC,UAAU,YAAY,CACzB;CAEA,SAAS,KAAK,QAA2B;EACvC,KAAU;GACR,GAAG;GACH,MAAM;GACN,OAAO,SAAS,MAAM,OAAO,MAAM;EACrC,CAAC;CACH;CAEA,SAAS,UAAU,MAAuB;EACxC,KAAU;GACR,GAAG;GACH,MAAM;GACN,OAAO,MAAM,MAAM,QAAQ,gBAAgB,YAAY,QAAQ,KAAK,GAAG;EACzE,CAAC;CACH;CAEA,SAAS,aAAa,MAA4B;EAChD,MAAM,YAAY;GAAE,GAAG;GAAO,SAAS;GAAM,MAAM;EAAE;EAErD,SAAS,SAAS;EAClB,KAAU,SAAS;CACrB;CAEA,SAAS,UAAU,QAA4B;EAC7C,aAAa,CAAC,GAAG,MAAM,SAAS,MAAM,CAAC;CACzC;CAEA,SAAS,aAAa,OAAe,QAA4B;EAC/D,aAAa,MAAM,QAAQ,KAAK,SAAS,aAAc,aAAa,QAAQ,SAAS,OAAQ,CAAC;CAChG;CAEA,SAAS,aAAa,OAAqB;EACzC,aAAa,MAAM,QAAQ,QAAQ,GAAG,YAAY,YAAY,KAAK,CAAC;CACtE;CAEA,SAAS,qBAAqB,OAAe,SAA+B;EAC1E,aAAa,CAAC,GAAG,MAAM,QAAQ,QAAQ,WAAW,OAAO,UAAU,KAAK,GAAG,GAAG,OAAO,CAAC;CACxF;CAEA,SAAS,eAAe,KAAa,OAAsB;EACzD,MAAM,OAAO,EAAE,GAAG,MAAM,aAAa;EAErC,IAAI,mBAAmB,KAAK,GAC1B,OAAO,KAAK;OAEZ,KAAK,OAAO;EAGd,MAAM,YAAY;GAAE,GAAG;GAAO,cAAc;GAAM,MAAM;EAAE;EAE1D,SAAS,SAAS;EAClB,KAAU,SAAS;CACrB;CAEA,SAAS,eAAqB;EAC5B,MAAM,YAAY;GAAE,GAAG;GAAO,SAAS,CAAC;GAAG,cAAc,CAAC;GAAG,MAAM;EAAE;EAErE,SAAS,SAAS;EAClB,KAAU,SAAS;CACrB;CAEA,MAAM,sBAAsB,YAC1B,OAAO,WAAmB,UAAqC;EAC7D,IAAI,CAAC,UACH,OAAO,CAAC;EAGV,MAAM,MAAM,IAAI,IAAI,UAAU,OAAO,SAAS,MAAM;EACpD,IAAI,aAAa,IAAI,WAAW,SAAS;EACzC,IAAI,aAAa,IAAI,KAAK,KAAK;EAE/B,MAAM,WAAW,MAAM,SAAS,GAAG,IAAI,WAAW,IAAI,UAAU;GAC9D,KAAK;GACL,cAAc;EAChB,CAAC;EAED,IAAI,CAAC,SAAS,IACZ,OAAO,CAAC;EAKV,QAAO,MAFe,SAAS,KAAK,GAEtB,WAAW,CAAC;CAC5B,GACA,CAAC,UAAU,YAAY,CACzB;CAEA,SAAS,SAAS,MAAoB;EACpC,KAAU;GACR,GAAG;GACH;EACF,CAAC;CACH;CAEA,MAAM,WAAW,kBAAwB;EACvC,IAAI,cAAc,CAAC,WAAW,SAC5B;EAGF,KACE;GACE,GAAG;GACH,MAAM,WAAW,YAAY,cAAc;EAC7C,GACA,IACF;CACF,GAAG;EAAC;EAAa;EAAM,WAAW;EAAS,WAAW;EAAU;EAAY;CAAK,CAAC;CAElF,gBAAgB;EACd,QAAQ,WAAW;EACnB,cAAc,iBAAiB;EAC/B,SAAS,YAAY;EACrB,cAAc,MAAM;EACpB,aAAa,CAAC,MAAM;CACtB,GAAG;EAAC;EAAa;EAAmB;EAAc;CAAM,CAAC;CAEzD,gBAAgB;EACd,IAAI,CAAC,UAAU,WACb;EAGF,KAAU,KAAK;CACjB,GAAG;EAAC;EAAW;EAAQ;EAAM;CAAK,CAAC;CAEnC,gBAAgB;EACd,SAAS,OAAO,OAAoB;GAGlC,IAFgB,MAA+B,QAEnC,cAAc,KAAK,IAC7B;GAGF,KAAU,KAAK;EACjB;EAEA,OAAO,iBAAiB,cAAc,iBAAiB,MAAM;EAE7D,aAAa,OAAO,oBAAoB,cAAc,iBAAiB,MAAM;CAC/E,GAAG;EAAC;EAAM,KAAK;EAAI;CAAK,CAAC;CAEzB,gBAAgB;EACd,IACE,CAAC,cACD,CAAC,WAAW,WACZ,cACA,CAAC,kBAAkB,WACnB,OAAO,yBAAyB,aAEhC;EAGF,MAAM,WAAW,IAAI,sBAClB,YAAY;GACX,IAAI,QAAQ,MAAM,UAAU,MAAM,cAAc,GAC9C,SAAS;EAEb,GACA,EACE,YAAY,QACd,CACF;EAEA,SAAS,QAAQ,kBAAkB,OAAO;EAE1C,aAAa,SAAS,WAAW;CACnC,GAAG;EAAC;EAAY;EAAU,WAAW;EAAS;CAAU,CAAC;CAEzD,OAAO;EACL;EACA;EACA;EACA;EACA,SAAS,MAAM;EACf,cAAc,MAAM;EACpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
@@ -1,4 +1,5 @@
1
1
  import { LATTICE_EVENT } from "../events/event-names.js";
2
+ import { isTranslatable } from "../i18n/translatable.js";
2
3
  //#region resources/js/toast/toast.ts
3
4
  var variants = [
4
5
  "success",
@@ -16,12 +17,14 @@ function isVariant(value) {
16
17
  function normalizeToastMessage(value) {
17
18
  if (typeof value !== "object" || value === null) return null;
18
19
  const toast = value;
19
- if (typeof toast.message !== "string" || toast.message === "") return null;
20
+ const rawMessage = toast.message;
21
+ const message = typeof rawMessage === "string" ? rawMessage : isTranslatable(rawMessage) ? rawMessage : null;
22
+ if (message === null || message === "") return null;
20
23
  return {
21
24
  action: toast.action ?? null,
22
25
  dismissible: toast.dismissible !== false,
23
26
  duration: typeof toast.duration === "number" ? toast.duration : null,
24
- message: toast.message,
27
+ message,
25
28
  persistent: toast.persistent === true,
26
29
  variant: isVariant(toast.variant) ? toast.variant : "success"
27
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"toast.js","names":[],"sources":["../../resources/js/toast/toast.ts"],"sourcesContent":["import type { ToastMessage, Variant } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\n\nexport type { ToastMessage, Variant };\n\nconst variants = [\"success\", \"info\", \"warning\", \"error\"] as const satisfies readonly Variant[];\n\nexport function isVariant(value: unknown): value is Variant {\n return variants.some((variant) => variant === value);\n}\n\n/**\n * Coerce a raw toast value (server flash payload or event detail) into a\n * ToastMessage, defaulting optional fields, or null when it is malformed.\n */\nexport function normalizeToastMessage(value: unknown): ToastMessage | null {\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n\n const toast = value as Record<string, unknown>;\n\n if (typeof toast.message !== \"string\" || toast.message === \"\") {\n return null;\n }\n\n return {\n action: (toast.action as ToastMessage[\"action\"]) ?? null,\n dismissible: toast.dismissible !== false,\n duration: typeof toast.duration === \"number\" ? toast.duration : null,\n message: toast.message,\n persistent: toast.persistent === true,\n variant: isVariant(toast.variant) ? toast.variant : \"success\",\n };\n}\n\n/** Coerce a `lattice:toast` event detail (`{ toast: {...} }`) into a ToastMessage. */\nexport function normalizeToast(detail: unknown): ToastMessage | null {\n if (typeof detail !== \"object\" || detail === null) {\n return null;\n }\n\n return normalizeToastMessage((detail as { toast?: unknown }).toast);\n}\n\n/** Emit a toast onto the shared `lattice:toast` bus. */\nexport function showToast(toast: ToastMessage): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n window.dispatchEvent(new CustomEvent(LATTICE_EVENT.toast, { detail: { toast } }));\n}\n\n/** Subscribe to the shared toast bus. Returns an unsubscribe function. */\nexport function onToast(callback: (toast: ToastMessage) => void): () => void {\n const listener = (event: Event): void => {\n const toast = normalizeToast((event as CustomEvent).detail);\n\n if (toast) {\n callback(toast);\n }\n };\n\n window.addEventListener(LATTICE_EVENT.toast, listener);\n\n return () => window.removeEventListener(LATTICE_EVENT.toast, listener);\n}\n"],"mappings":";;AAKA,IAAM,WAAW;CAAC;CAAW;CAAQ;CAAW;AAAO;AAEvD,SAAgB,UAAU,OAAkC;CAC1D,OAAO,SAAS,MAAM,YAAY,YAAY,KAAK;AACrD;;;;;AAMA,SAAgB,sBAAsB,OAAqC;CACzE,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;CAGT,MAAM,QAAQ;CAEd,IAAI,OAAO,MAAM,YAAY,YAAY,MAAM,YAAY,IACzD,OAAO;CAGT,OAAO;EACL,QAAS,MAAM,UAAqC;EACpD,aAAa,MAAM,gBAAgB;EACnC,UAAU,OAAO,MAAM,aAAa,WAAW,MAAM,WAAW;EAChE,SAAS,MAAM;EACf,YAAY,MAAM,eAAe;EACjC,SAAS,UAAU,MAAM,OAAO,IAAI,MAAM,UAAU;CACtD;AACF;;AAGA,SAAgB,eAAe,QAAsC;CACnE,IAAI,OAAO,WAAW,YAAY,WAAW,MAC3C,OAAO;CAGT,OAAO,sBAAuB,OAA+B,KAAK;AACpE;;AAGA,SAAgB,UAAU,OAA2B;CACnD,IAAI,OAAO,WAAW,aACpB;CAGF,OAAO,cAAc,IAAI,YAAY,cAAc,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAClF;;AAGA,SAAgB,QAAQ,UAAqD;CAC3E,MAAM,YAAY,UAAuB;EACvC,MAAM,QAAQ,eAAgB,MAAsB,MAAM;EAE1D,IAAI,OACF,SAAS,KAAK;CAElB;CAEA,OAAO,iBAAiB,cAAc,OAAO,QAAQ;CAErD,aAAa,OAAO,oBAAoB,cAAc,OAAO,QAAQ;AACvE"}
1
+ {"version":3,"file":"toast.js","names":[],"sources":["../../resources/js/toast/toast.ts"],"sourcesContent":["import type { ToastMessage, Variant } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { isTranslatable } from \"@lattice-php/lattice/i18n/translatable\";\n\nexport type { ToastMessage, Variant };\n\nconst variants = [\"success\", \"info\", \"warning\", \"error\"] as const satisfies readonly Variant[];\n\nexport function isVariant(value: unknown): value is Variant {\n return variants.some((variant) => variant === value);\n}\n\n/**\n * Coerce a raw toast value (server flash payload or event detail) into a\n * ToastMessage, defaulting optional fields, or null when it is malformed.\n */\nexport function normalizeToastMessage(value: unknown): ToastMessage | null {\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n\n const toast = value as Record<string, unknown>;\n\n const rawMessage = toast.message;\n const message =\n typeof rawMessage === \"string\" ? rawMessage : isTranslatable(rawMessage) ? rawMessage : null;\n\n if (message === null || message === \"\") {\n return null;\n }\n\n return {\n action: (toast.action as ToastMessage[\"action\"]) ?? null,\n dismissible: toast.dismissible !== false,\n duration: typeof toast.duration === \"number\" ? toast.duration : null,\n message,\n persistent: toast.persistent === true,\n variant: isVariant(toast.variant) ? toast.variant : \"success\",\n };\n}\n\n/** Coerce a `lattice:toast` event detail (`{ toast: {...} }`) into a ToastMessage. */\nexport function normalizeToast(detail: unknown): ToastMessage | null {\n if (typeof detail !== \"object\" || detail === null) {\n return null;\n }\n\n return normalizeToastMessage((detail as { toast?: unknown }).toast);\n}\n\n/** Emit a toast onto the shared `lattice:toast` bus. */\nexport function showToast(toast: ToastMessage): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n window.dispatchEvent(new CustomEvent(LATTICE_EVENT.toast, { detail: { toast } }));\n}\n\n/** Subscribe to the shared toast bus. Returns an unsubscribe function. */\nexport function onToast(callback: (toast: ToastMessage) => void): () => void {\n const listener = (event: Event): void => {\n const toast = normalizeToast((event as CustomEvent).detail);\n\n if (toast) {\n callback(toast);\n }\n };\n\n window.addEventListener(LATTICE_EVENT.toast, listener);\n\n return () => window.removeEventListener(LATTICE_EVENT.toast, listener);\n}\n"],"mappings":";;;AAMA,IAAM,WAAW;CAAC;CAAW;CAAQ;CAAW;AAAO;AAEvD,SAAgB,UAAU,OAAkC;CAC1D,OAAO,SAAS,MAAM,YAAY,YAAY,KAAK;AACrD;;;;;AAMA,SAAgB,sBAAsB,OAAqC;CACzE,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;CAGT,MAAM,QAAQ;CAEd,MAAM,aAAa,MAAM;CACzB,MAAM,UACJ,OAAO,eAAe,WAAW,aAAa,eAAe,UAAU,IAAI,aAAa;CAE1F,IAAI,YAAY,QAAQ,YAAY,IAClC,OAAO;CAGT,OAAO;EACL,QAAS,MAAM,UAAqC;EACpD,aAAa,MAAM,gBAAgB;EACnC,UAAU,OAAO,MAAM,aAAa,WAAW,MAAM,WAAW;EAChE;EACA,YAAY,MAAM,eAAe;EACjC,SAAS,UAAU,MAAM,OAAO,IAAI,MAAM,UAAU;CACtD;AACF;;AAGA,SAAgB,eAAe,QAAsC;CACnE,IAAI,OAAO,WAAW,YAAY,WAAW,MAC3C,OAAO;CAGT,OAAO,sBAAuB,OAA+B,KAAK;AACpE;;AAGA,SAAgB,UAAU,OAA2B;CACnD,IAAI,OAAO,WAAW,aACpB;CAGF,OAAO,cAAc,IAAI,YAAY,cAAc,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAClF;;AAGA,SAAgB,QAAQ,UAAqD;CAC3E,MAAM,YAAY,UAAuB;EACvC,MAAM,QAAQ,eAAgB,MAAsB,MAAM;EAE1D,IAAI,OACF,SAAS,KAAK;CAElB;CAEA,OAAO,iBAAiB,cAAc,OAAO,QAAQ;CAErD,aAAa,OAAO,oBAAoB,cAAc,OAAO,QAAQ;AACvE"}
@@ -1,6 +1,7 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { Icon } from "../icons/sprite.js";
3
3
  import { useT } from "../i18n/instance.js";
4
+ import { resolveTranslatable } from "../i18n/translatable.js";
4
5
  import { onToast } from "./toast.js";
5
6
  import { RenderNode } from "../core/renderer.js";
6
7
  import { variantStyles } from "./variant-styles.js";
@@ -25,7 +26,7 @@ function Toaster({ duration = 4e3 }) {
25
26
  duration,
26
27
  swipeDirection: "down",
27
28
  children: [toasts.map((toast) => /* @__PURE__ */ jsxs(Toast.Root, {
28
- className: cn("flex items-start gap-3 rounded-lt border border-l-4 border-lt-border bg-lt-popover p-4 text-lt-popover-fg shadow-lg", variantStyles[toast.variant].accent, "data-[state=open]:animate-lt-toast-in data-[state=closed]:animate-lt-toast-out", "data-[swipe=move]:translate-y-[var(--radix-toast-swipe-move-y)] data-[swipe=cancel]:translate-y-0 data-[swipe=cancel]:transition-transform"),
29
+ className: cn("flex items-start gap-3 rounded-lt border border-l-4 border-lt-border bg-lt-popover p-4 text-lt-popover-fg shadow-lt-lg", variantStyles[toast.variant].accent, "data-[state=open]:animate-lt-toast-in data-[state=closed]:animate-lt-toast-out", "data-[swipe=move]:translate-y-[var(--radix-toast-swipe-move-y)] data-[swipe=cancel]:translate-y-0 data-[swipe=cancel]:transition-transform"),
29
30
  "data-test": `toast-${toast.variant}`,
30
31
  duration: toast.persistent ? Infinity : toast.duration ?? duration,
31
32
  onOpenChange: (open) => {
@@ -37,7 +38,7 @@ function Toaster({ duration = 4e3 }) {
37
38
  className: "flex min-w-0 flex-1 flex-col gap-2",
38
39
  children: [/* @__PURE__ */ jsx(Toast.Title, {
39
40
  className: "text-sm text-lt-fg",
40
- children: toast.message
41
+ children: typeof toast.message === "string" ? toast.message : resolveTranslatable(toast.message, {}, t)
41
42
  }), toast.action ? /* @__PURE__ */ jsx("div", {
42
43
  className: "flex flex-wrap gap-2",
43
44
  children: /* @__PURE__ */ jsx(RenderNode, { node: toast.action })
@@ -45,7 +46,7 @@ function Toaster({ duration = 4e3 }) {
45
46
  }),
46
47
  toast.dismissible ? /* @__PURE__ */ jsx(Toast.Close, {
47
48
  "aria-label": t("a11y.dismiss", "Dismiss"),
48
- className: "shrink-0 rounded-md p-1 text-lt-muted-fg transition-colors hover:bg-lt-muted hover:text-lt-fg",
49
+ className: "shrink-0 rounded-lt-sm p-1 text-lt-muted-fg transition-colors hover:bg-lt-muted hover:text-lt-fg",
49
50
  "data-test": "toast-dismiss",
50
51
  children: /* @__PURE__ */ jsx(Icon, {
51
52
  name: "x",
@@ -1 +1 @@
1
- {"version":3,"file":"toaster.js","names":[],"sources":["../../resources/js/toast/toaster.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport * as Toast from \"@radix-ui/react-toast\";\nimport { useEffect, useState } from \"react\";\nimport { RenderNode } from \"@lattice-php/lattice/core/renderer\";\nimport type { ToastMessage } from \"@lattice-php/lattice/types/generated\";\nimport { onToast } from \"@lattice-php/lattice/toast/toast\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { variantStyles } from \"@lattice-php/lattice/toast/variant-styles\";\n\ntype ToastItem = ToastMessage & { id: number };\n\nlet nextId = 0;\n\nexport function Toaster({ duration = 4000 }: { duration?: number }) {\n const { t } = useT(\"lattice\");\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n\n function dismiss(id: number): void {\n setToasts((current) => current.filter((toast) => toast.id !== id));\n }\n\n useEffect(\n () =>\n onToast((toast) => {\n setToasts((current) => [...current, { ...toast, id: nextId++ }]);\n }),\n [],\n );\n\n return (\n <Toast.Provider duration={duration} swipeDirection=\"down\">\n {toasts.map((toast) => (\n <Toast.Root\n key={toast.id}\n className={cn(\n \"flex items-start gap-3 rounded-lt border border-l-4 border-lt-border bg-lt-popover p-4 text-lt-popover-fg shadow-lg\",\n variantStyles[toast.variant].accent,\n \"data-[state=open]:animate-lt-toast-in data-[state=closed]:animate-lt-toast-out\",\n \"data-[swipe=move]:translate-y-[var(--radix-toast-swipe-move-y)] data-[swipe=cancel]:translate-y-0 data-[swipe=cancel]:transition-transform\",\n )}\n data-test={`toast-${toast.variant}`}\n duration={toast.persistent ? Infinity : (toast.duration ?? duration)}\n onOpenChange={(open) => {\n if (!open) {\n dismiss(toast.id);\n }\n }}\n >\n {variantStyles[toast.variant].icon}\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <Toast.Title className=\"text-sm text-lt-fg\">{toast.message}</Toast.Title>\n {toast.action ? (\n <div className=\"flex flex-wrap gap-2\">\n <RenderNode node={toast.action} />\n </div>\n ) : null}\n </div>\n {toast.dismissible ? (\n <Toast.Close\n aria-label={t(\"a11y.dismiss\", \"Dismiss\")}\n className=\"shrink-0 rounded-md p-1 text-lt-muted-fg transition-colors hover:bg-lt-muted hover:text-lt-fg\"\n data-test=\"toast-dismiss\"\n >\n <Icon name=\"x\" className=\"size-lt-icon-md\" />\n </Toast.Close>\n ) : null}\n </Toast.Root>\n ))}\n <Toast.Viewport className=\"fixed inset-x-0 bottom-0 z-[100] mx-auto flex w-full max-w-sm flex-col gap-2 p-4 outline-none\" />\n </Toast.Provider>\n );\n}\n"],"mappings":";;;;;;;;;;AAYA,IAAI,SAAS;AAEb,SAAgB,QAAQ,EAAE,WAAW,OAA+B;CAClE,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,QAAQ,aAAa,SAAsB,CAAC,CAAC;CAEpD,SAAS,QAAQ,IAAkB;EACjC,WAAW,YAAY,QAAQ,QAAQ,UAAU,MAAM,OAAO,EAAE,CAAC;CACnE;CAEA,gBAEI,SAAS,UAAU;EACjB,WAAW,YAAY,CAAC,GAAG,SAAS;GAAE,GAAG;GAAO,IAAI;EAAS,CAAC,CAAC;CACjE,CAAC,GACH,CAAC,CACH;CAEA,OACE,qBAAC,MAAM,UAAP;EAA0B;EAAU,gBAAe;YAAnD,CACG,OAAO,KAAK,UACX,qBAAC,MAAM,MAAP;GAEE,WAAW,GACT,uHACA,cAAc,MAAM,SAAS,QAC7B,kFACA,4IACF;GACA,aAAW,SAAS,MAAM;GAC1B,UAAU,MAAM,aAAa,WAAY,MAAM,YAAY;GAC3D,eAAe,SAAS;IACtB,IAAI,CAAC,MACH,QAAQ,MAAM,EAAE;GAEpB;aAdF;IAgBG,cAAc,MAAM,SAAS;IAC9B,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,MAAM,OAAP;MAAa,WAAU;gBAAsB,MAAM;KAAqB,CAAA,GACvE,MAAM,SACL,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,YAAD,EAAY,MAAM,MAAM,OAAS,CAAA;KAC9B,CAAA,IACH,IACD;;IACJ,MAAM,cACL,oBAAC,MAAM,OAAP;KACE,cAAY,EAAE,gBAAgB,SAAS;KACvC,WAAU;KACV,aAAU;eAEV,oBAAC,MAAD;MAAM,MAAK;MAAI,WAAU;KAAmB,CAAA;IACjC,CAAA,IACX;GACM;KAjCL,MAAM,EAiCD,CACb,GACD,oBAAC,MAAM,UAAP,EAAgB,WAAU,gGAAiG,CAAA,CAC7G;;AAEpB"}
1
+ {"version":3,"file":"toaster.js","names":[],"sources":["../../resources/js/toast/toaster.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport * as Toast from \"@radix-ui/react-toast\";\nimport { useEffect, useState } from \"react\";\nimport { RenderNode } from \"@lattice-php/lattice/core/renderer\";\nimport type { ToastMessage } from \"@lattice-php/lattice/types/generated\";\nimport { onToast } from \"@lattice-php/lattice/toast/toast\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { variantStyles } from \"@lattice-php/lattice/toast/variant-styles\";\nimport { resolveTranslatable } from \"@lattice-php/lattice/i18n/translatable\";\n\ntype ToastItem = ToastMessage & { id: number };\n\nlet nextId = 0;\n\nexport function Toaster({ duration = 4000 }: { duration?: number }) {\n const { t } = useT(\"lattice\");\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n\n function dismiss(id: number): void {\n setToasts((current) => current.filter((toast) => toast.id !== id));\n }\n\n useEffect(\n () =>\n onToast((toast) => {\n setToasts((current) => [...current, { ...toast, id: nextId++ }]);\n }),\n [],\n );\n\n return (\n <Toast.Provider duration={duration} swipeDirection=\"down\">\n {toasts.map((toast) => (\n <Toast.Root\n key={toast.id}\n className={cn(\n \"flex items-start gap-3 rounded-lt border border-l-4 border-lt-border bg-lt-popover p-4 text-lt-popover-fg shadow-lt-lg\",\n variantStyles[toast.variant].accent,\n \"data-[state=open]:animate-lt-toast-in data-[state=closed]:animate-lt-toast-out\",\n \"data-[swipe=move]:translate-y-[var(--radix-toast-swipe-move-y)] data-[swipe=cancel]:translate-y-0 data-[swipe=cancel]:transition-transform\",\n )}\n data-test={`toast-${toast.variant}`}\n duration={toast.persistent ? Infinity : (toast.duration ?? duration)}\n onOpenChange={(open) => {\n if (!open) {\n dismiss(toast.id);\n }\n }}\n >\n {variantStyles[toast.variant].icon}\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <Toast.Title className=\"text-sm text-lt-fg\">\n {typeof toast.message === \"string\"\n ? toast.message\n : resolveTranslatable(toast.message, {}, t)}\n </Toast.Title>\n {toast.action ? (\n <div className=\"flex flex-wrap gap-2\">\n <RenderNode node={toast.action} />\n </div>\n ) : null}\n </div>\n {toast.dismissible ? (\n <Toast.Close\n aria-label={t(\"a11y.dismiss\", \"Dismiss\")}\n className=\"shrink-0 rounded-lt-sm p-1 text-lt-muted-fg transition-colors hover:bg-lt-muted hover:text-lt-fg\"\n data-test=\"toast-dismiss\"\n >\n <Icon name=\"x\" className=\"size-lt-icon-md\" />\n </Toast.Close>\n ) : null}\n </Toast.Root>\n ))}\n <Toast.Viewport className=\"fixed inset-x-0 bottom-0 z-[100] mx-auto flex w-full max-w-sm flex-col gap-2 p-4 outline-none\" />\n </Toast.Provider>\n );\n}\n"],"mappings":";;;;;;;;;;;AAaA,IAAI,SAAS;AAEb,SAAgB,QAAQ,EAAE,WAAW,OAA+B;CAClE,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,QAAQ,aAAa,SAAsB,CAAC,CAAC;CAEpD,SAAS,QAAQ,IAAkB;EACjC,WAAW,YAAY,QAAQ,QAAQ,UAAU,MAAM,OAAO,EAAE,CAAC;CACnE;CAEA,gBAEI,SAAS,UAAU;EACjB,WAAW,YAAY,CAAC,GAAG,SAAS;GAAE,GAAG;GAAO,IAAI;EAAS,CAAC,CAAC;CACjE,CAAC,GACH,CAAC,CACH;CAEA,OACE,qBAAC,MAAM,UAAP;EAA0B;EAAU,gBAAe;YAAnD,CACG,OAAO,KAAK,UACX,qBAAC,MAAM,MAAP;GAEE,WAAW,GACT,0HACA,cAAc,MAAM,SAAS,QAC7B,kFACA,4IACF;GACA,aAAW,SAAS,MAAM;GAC1B,UAAU,MAAM,aAAa,WAAY,MAAM,YAAY;GAC3D,eAAe,SAAS;IACtB,IAAI,CAAC,MACH,QAAQ,MAAM,EAAE;GAEpB;aAdF;IAgBG,cAAc,MAAM,SAAS;IAC9B,qBAAC,OAAD;KAAK,WAAU;eAAf,CACE,oBAAC,MAAM,OAAP;MAAa,WAAU;gBACpB,OAAO,MAAM,YAAY,WACtB,MAAM,UACN,oBAAoB,MAAM,SAAS,CAAC,GAAG,CAAC;KACjC,CAAA,GACZ,MAAM,SACL,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,YAAD,EAAY,MAAM,MAAM,OAAS,CAAA;KAC9B,CAAA,IACH,IACD;;IACJ,MAAM,cACL,oBAAC,MAAM,OAAP;KACE,cAAY,EAAE,gBAAgB,SAAS;KACvC,WAAU;KACV,aAAU;eAEV,oBAAC,MAAD;MAAM,MAAK;MAAI,WAAU;KAAmB,CAAA;IACjC,CAAA,IACX;GACM;KArCL,MAAM,EAqCD,CACb,GACD,oBAAC,MAAM,UAAP,EAAgB,WAAU,gGAAiG,CAAA,CAC7G;;AAEpB"}
@@ -37,6 +37,10 @@ export type ActionResult = {
37
37
  readonly data: Record<string, unknown>;
38
38
  readonly effects: Effect[];
39
39
  };
40
+ export type Affix = {
41
+ readonly icon: string | null;
42
+ readonly text: string | null;
43
+ };
40
44
  export type Align = "center" | "left" | "start" | "stretch";
41
45
  export type Badge = {
42
46
  label: string;
@@ -46,6 +50,13 @@ export type BadgeColumn = {
46
50
  };
47
51
  export type BooleanColumn = Record<string, never>;
48
52
  export type Breadcrumbs = Record<string, never>;
53
+ export type BrowserToken = {
54
+ readonly accessToken: string;
55
+ readonly tokenType: string;
56
+ readonly expiresIn: number;
57
+ readonly audience: string;
58
+ readonly scopes: string[];
59
+ };
49
60
  export type Builder = {
50
61
  addLabel: string | null;
51
62
  columnWidth: ColumnWidth;
@@ -113,6 +124,7 @@ export type Card = {
113
124
  description: string | null;
114
125
  title: string | null;
115
126
  };
127
+ export type ChannelVisibility = "public" | "private" | "presence";
116
128
  export type Chart = {
117
129
  categoryKey: string | null;
118
130
  data: Record<string, unknown>[];
@@ -146,7 +158,7 @@ export type ChatBox = {
146
158
  export type ChatMessage = {
147
159
  readonly id: string;
148
160
  readonly role: ChatRole;
149
- readonly parts: ChatNode[];
161
+ readonly parts: Node[];
150
162
  };
151
163
  export type ChatNode = {
152
164
  type: "chat.part.text";
@@ -234,6 +246,16 @@ export type ColumnFilter = {
234
246
  readonly options: Option[];
235
247
  readonly multiple: boolean;
236
248
  readonly searchable: boolean;
249
+ readonly clauseOptions?: ColumnFilterOption[];
250
+ };
251
+ export type ColumnFilterOption = {
252
+ readonly label: string;
253
+ readonly value: string;
254
+ readonly clauses: ColumnFilterOptionClause[];
255
+ };
256
+ export type ColumnFilterOptionClause = {
257
+ readonly operator: Op;
258
+ readonly value: string;
237
259
  };
238
260
  export type ColumnPropsMap = {
239
261
  "column.badge": BadgeColumn;
@@ -339,6 +361,11 @@ export type CoreNode = {
339
361
  key?: string;
340
362
  props: Text;
341
363
  };
364
+ export type DataList = {
365
+ dataEndpoint: string | null;
366
+ emptyLabel: string | null;
367
+ remote: RemoteAccess | null;
368
+ };
342
369
  export type DateInput = {
343
370
  autoFocus: boolean;
344
371
  columnWidth: ColumnWidth;
@@ -628,6 +655,11 @@ export type LayoutNode = {
628
655
  key?: string;
629
656
  props: Sidebar;
630
657
  schema?: Node[];
658
+ } | {
659
+ type: "topbar";
660
+ key?: string;
661
+ props: Topbar;
662
+ schema?: Node[];
631
663
  };
632
664
  export type Link = {
633
665
  href: string | null;
@@ -635,6 +667,12 @@ export type Link = {
635
667
  method: HttpMethod | null;
636
668
  tabIndex: number | null;
637
669
  };
670
+ export type ListenerPayload = {
671
+ readonly channel: string;
672
+ readonly visibility: ChannelVisibility;
673
+ readonly events: string[];
674
+ readonly effects: Effect[];
675
+ };
638
676
  export type LocaleChangeEffect = {
639
677
  readonly locale: string;
640
678
  };
@@ -644,6 +682,8 @@ export type MenuItem = {
644
682
  icon: string | null;
645
683
  label: string;
646
684
  method: HttpMethod | null;
685
+ prefix: Affix | null;
686
+ suffix: Affix | null;
647
687
  };
648
688
  export type Modal = {
649
689
  closeLabel: string;
@@ -658,7 +698,7 @@ export type MoneyColumn = {
658
698
  maximumFractionDigits: number | null;
659
699
  minimumFractionDigits: number | null;
660
700
  };
661
- export type Node = FormNode | CoreNode | ActionNode | FragmentNode | TableNode | LayoutNode | ChatNode;
701
+ export type Node = FormNode | CoreNode | ActionNode | FragmentNode | RemoteNode | TableNode | LayoutNode | ChatNode;
662
702
  export type NodeType = Node["type"];
663
703
  export type NumberColumn = {
664
704
  maximumFractionDigits: number | null;
@@ -688,10 +728,12 @@ export type NumberInput = {
688
728
  placeholder: string | null;
689
729
  prefillRefreshOn: string[] | null;
690
730
  prefillResetOn: string[] | null;
731
+ prefix: Affix | null;
691
732
  readOnly: boolean;
692
733
  required: boolean;
693
734
  slider: boolean;
694
735
  step: number | null;
736
+ suffix: Affix | null;
695
737
  tabIndex: number | null;
696
738
  tooltip: string | null;
697
739
  value: unknown;
@@ -741,8 +783,10 @@ export type PasswordInput = {
741
783
  placeholder: string | null;
742
784
  prefillRefreshOn: string[] | null;
743
785
  prefillResetOn: string[] | null;
786
+ prefix: Affix | null;
744
787
  readOnly: boolean;
745
788
  required: boolean;
789
+ suffix: Affix | null;
746
790
  tabIndex: number | null;
747
791
  tooltip: string | null;
748
792
  value: unknown;
@@ -758,6 +802,34 @@ export type ReloadComponentEffect = {
758
802
  readonly component: string;
759
803
  };
760
804
  export type ReloadPageEffect = object;
805
+ export type RemoteAccess = {
806
+ readonly source: string;
807
+ readonly audience: string;
808
+ readonly scopes: string[];
809
+ readonly nodeId: string;
810
+ readonly nodeType: string;
811
+ readonly tokenEndpoint: string;
812
+ readonly ref: string;
813
+ };
814
+ export type RemoteChatBox = {
815
+ conversationId: string | null;
816
+ fill: boolean;
817
+ historyEndpoint: string | null;
818
+ placeholder: string | null;
819
+ remote: RemoteAccess | null;
820
+ streamEndpoint: string | null;
821
+ title: string | null;
822
+ };
823
+ export type RemoteNode = {
824
+ type: "remote.chat-box";
825
+ key?: string;
826
+ props: RemoteChatBox;
827
+ } | {
828
+ type: "remote.data-list";
829
+ key?: string;
830
+ props: DataList;
831
+ schema?: Node[];
832
+ };
761
833
  export type Repeater = {
762
834
  addLabel: string | null;
763
835
  columnWidth: ColumnWidth;
@@ -873,6 +945,7 @@ export type Select = {
873
945
  tooltip: string | null;
874
946
  value: unknown;
875
947
  };
948
+ export type Side = "start" | "end";
876
949
  export type Sidebar = {
877
950
  collapsible: boolean;
878
951
  rememberState: boolean;
@@ -882,6 +955,7 @@ export type SortDirection = "asc" | "desc";
882
955
  export type Stack = {
883
956
  align: Align | null;
884
957
  direction: string | null;
958
+ float: Side | null;
885
959
  gap: Gap | null;
886
960
  height: Height | null;
887
961
  justify: Justify | null;
@@ -981,8 +1055,10 @@ export type TextInput = {
981
1055
  placeholder: string | null;
982
1056
  prefillRefreshOn: string[] | null;
983
1057
  prefillResetOn: string[] | null;
1058
+ prefix: Affix | null;
984
1059
  readOnly: boolean;
985
1060
  required: boolean;
1061
+ suffix: Affix | null;
986
1062
  tabIndex: number | null;
987
1063
  tooltip: string | null;
988
1064
  type: string | null;
@@ -1027,11 +1103,19 @@ export type ToastMessage = {
1027
1103
  dismissible: boolean;
1028
1104
  action: Node | null;
1029
1105
  variant: Variant;
1030
- message: string;
1106
+ message: Translatable | string;
1031
1107
  };
1032
1108
  export type ToolCallPart = {
1033
1109
  args: Record<string, unknown>;
1034
1110
  name: string;
1035
1111
  };
1112
+ export type Topbar = {
1113
+ sticky: boolean;
1114
+ };
1115
+ export type Translatable = {
1116
+ payload: Record<string, string>;
1117
+ replacements: Record<string, string | number | boolean>;
1118
+ key: string;
1119
+ };
1036
1120
  export type Variant = "success" | "info" | "warning" | "error";
1037
- export type Width = "full" | "sm" | "md" | "lg" | "fill";
1121
+ export type Width = "full" | "auto" | "sm" | "md" | "lg" | "fill";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lattice-php/lattice",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Server-driven React components for Laravel and Inertia.",
5
5
  "license": "MIT",
6
6
  "author": "Manuel Christlieb <manuel@christlieb.eu>",
@@ -127,6 +127,10 @@
127
127
  "types": "./dist/provider.d.ts",
128
128
  "import": "./dist/provider.js"
129
129
  },
130
+ "./realtime/*": {
131
+ "types": "./dist/realtime/*.d.ts",
132
+ "import": "./dist/realtime/*.js"
133
+ },
130
134
  "./registry": {
131
135
  "types": "./dist/registry.d.ts",
132
136
  "import": "./dist/registry.js"
@@ -223,9 +227,19 @@
223
227
  },
224
228
  "peerDependencies": {
225
229
  "@inertiajs/react": "^3.0.0",
230
+ "@laravel/echo-react": "^2.0.0",
231
+ "pusher-js": "^8.0.0",
226
232
  "react": "^19.0.0",
227
233
  "react-dom": "^19.0.0"
228
234
  },
235
+ "peerDependenciesMeta": {
236
+ "@laravel/echo-react": {
237
+ "optional": true
238
+ },
239
+ "pusher-js": {
240
+ "optional": true
241
+ }
242
+ },
229
243
  "devDependencies": {
230
244
  "@astrojs/react": "^5.0.7",
231
245
  "@astrojs/starlight": "^0.40.0",
@@ -233,6 +247,7 @@
233
247
  "@codecov/vite-plugin": "^2.0.1",
234
248
  "@inertiajs/react": "^3.0.0",
235
249
  "@inertiajs/vite": "^3.0.0",
250
+ "@laravel/echo-react": "^2.3.7",
236
251
  "@tailwindcss/vite": "^4.1.11",
237
252
  "@testing-library/dom": "^10.4.1",
238
253
  "@testing-library/jest-dom": "^6.9.1",
@@ -243,12 +258,16 @@
243
258
  "@vitejs/plugin-react": "^6.0.2",
244
259
  "@vitest/coverage-v8": "^4.1.8",
245
260
  "astro": "7.0.0-beta.3",
261
+ "astro-og-canvas": "^0.11.1",
262
+ "canvaskit-wasm": "^0.41.1",
246
263
  "jsdom": "^29.1.1",
264
+ "laravel-echo": "^2.3.7",
247
265
  "laravel-vite-plugin": "^3.0.0",
248
266
  "lucide-static": "^1.18.0",
249
267
  "oxfmt": "^0.54.0",
250
268
  "oxlint": "^1.68.0",
251
269
  "playwright": "^1.60.0",
270
+ "pusher-js": "^8.5.0",
252
271
  "react": "^19.2.0",
253
272
  "react-dom": "^19.2.0",
254
273
  "react-scan": "^0.5.7",