@lattice-php/lattice 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action/components/action-form.d.ts +6 -1
- package/dist/action/components/action-form.js +34 -49
- package/dist/action/components/action-form.js.map +1 -1
- package/dist/action/components/action-trigger-provider.d.ts +1 -1
- package/dist/action/components/action-trigger-provider.js.map +1 -1
- package/dist/action/hooks/use-action.d.ts +1 -1
- package/dist/action/hooks/use-action.js +3 -1
- package/dist/action/hooks/use-action.js.map +1 -1
- package/dist/action/index.d.ts +0 -2
- package/dist/action/index.js +1 -2
- package/dist/chat/plugin.d.ts +0 -1
- package/dist/chat/plugin.js.map +1 -1
- package/dist/clipboard/index.d.ts +0 -4
- package/dist/clipboard/index.js +1 -15
- package/dist/{events → core}/event-names.js +1 -1
- package/dist/core/event-names.js.map +1 -0
- package/dist/core/hooks/use-window-event.d.ts +8 -0
- package/dist/core/hooks/use-window-event.js +22 -0
- package/dist/core/hooks/use-window-event.js.map +1 -0
- package/dist/core/index.js +1 -1
- package/dist/core/types.d.ts +8 -61
- package/dist/create-app.d.ts +31 -3
- package/dist/create-app.js +48 -10
- package/dist/create-app.js.map +1 -1
- package/dist/effects/dispatch.js +1 -1
- package/dist/effects/dispatch.js.map +1 -1
- package/dist/effects/registry.d.ts +2 -18
- package/dist/effects/registry.js +5 -9
- package/dist/effects/registry.js.map +1 -1
- package/dist/event-bridge.js +10 -23
- package/dist/event-bridge.js.map +1 -1
- package/dist/form/components/base/field.d.ts +2 -2
- package/dist/form/components/base/field.js +2 -2
- package/dist/form/components/base/field.js.map +1 -1
- package/dist/form/components/fields/color-picker-field.d.ts +2 -0
- package/dist/form/components/fields/color-picker-field.js +57 -0
- package/dist/form/components/fields/color-picker-field.js.map +1 -0
- package/dist/form/components/fields/date-input.js +1 -1
- package/dist/form/components/fields/date-time-input.js +1 -1
- package/dist/form/components/fields/file-upload.js +11 -18
- package/dist/form/components/fields/file-upload.js.map +1 -1
- package/dist/form/components/fields/number-input.js +1 -1
- package/dist/form/components/fields/rich-editor-field.js +35 -276
- package/dist/form/components/fields/rich-editor-field.js.map +1 -1
- package/dist/form/components/fields/row-action-menu.js +2 -2
- package/dist/form/components/fields/row-action-menu.js.map +1 -1
- package/dist/form/components/fields/row-actions.js +3 -1
- package/dist/form/components/fields/row-actions.js.map +1 -1
- package/dist/form/components/fields/select.js +60 -17
- package/dist/form/components/fields/select.js.map +1 -1
- package/dist/form/components/fields/table-rows.js +5 -17
- package/dist/form/components/fields/table-rows.js.map +1 -1
- package/dist/form/components/fields/text-input.js +8 -1
- package/dist/form/components/fields/text-input.js.map +1 -1
- package/dist/form/components/fields/time-input.js +1 -1
- package/dist/form/components/form.js +8 -23
- package/dist/form/components/form.js.map +1 -1
- package/dist/form/components/index.d.ts +1 -0
- package/dist/form/components/index.js +2 -1
- package/dist/form/embed.d.ts +5 -0
- package/dist/form/embed.js +4 -1
- package/dist/form/hooks/use-form-resolver.js.map +1 -1
- package/dist/form/index.js +2 -1
- package/dist/form/lib/collect-fields.d.ts +7 -0
- package/dist/form/lib/collect-fields.js +19 -0
- package/dist/form/lib/collect-fields.js.map +1 -0
- package/dist/form/lib/conditions.d.ts +4 -10
- package/dist/form/lib/conditions.js.map +1 -1
- package/dist/form/lib/field-errors.d.ts +3 -0
- package/dist/form/lib/field-errors.js +11 -0
- package/dist/form/lib/field-errors.js.map +1 -0
- package/dist/form/lib/field-props.d.ts +4 -18
- package/dist/form/lib/field-props.js.map +1 -1
- package/dist/form/plugin.js +2 -0
- package/dist/form/plugin.js.map +1 -1
- package/dist/form/rich-editor/builtins.d.ts +6 -0
- package/dist/form/rich-editor/builtins.js +400 -0
- package/dist/form/rich-editor/builtins.js.map +1 -0
- package/dist/form/rich-editor/index.d.ts +7 -0
- package/dist/form/rich-editor/index.js +3 -0
- package/dist/form/rich-editor/registry.d.ts +67 -0
- package/dist/form/rich-editor/registry.js +79 -0
- package/dist/form/rich-editor/registry.js.map +1 -0
- package/dist/form/rich-editor/toolbar-button.d.ts +12 -0
- package/dist/form/rich-editor/toolbar-button.js +28 -0
- package/dist/form/rich-editor/toolbar-button.js.map +1 -0
- package/dist/form/types.d.ts +1 -6
- package/dist/format/date-time.d.ts +3 -2
- package/dist/format/date-time.js.map +1 -1
- package/dist/format/format-context.d.ts +6 -0
- package/dist/format/format-context.js +17 -0
- package/dist/format/format-context.js.map +1 -0
- package/dist/i18n/date-time.d.ts +3 -2
- package/dist/i18n/date-time.js.map +1 -1
- package/dist/i18n/index.js +3 -2
- package/dist/i18n/locale-reload.d.ts +1 -1
- package/dist/i18n/locale-reload.js +7 -6
- package/dist/i18n/locale-reload.js.map +1 -1
- package/dist/i18n/locale.js +1 -1
- package/dist/i18n/locale.js.map +1 -1
- package/dist/i18n/page-props.d.ts +3 -2
- package/dist/i18n/page-props.js +1 -15
- package/dist/i18n/page-props.js.map +1 -1
- package/dist/i18n/shared-props.d.ts +7 -0
- package/dist/i18n/shared-props.js +23 -0
- package/dist/i18n/shared-props.js.map +1 -0
- package/dist/i18n/timezone.js +1 -1
- package/dist/i18n/timezone.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +5 -5
- package/dist/lattice.css +212 -47
- package/dist/layout/components/breadcrumbs.js +3 -1
- package/dist/layout/components/breadcrumbs.js.map +1 -1
- package/dist/layout/components/sidebar.js +15 -48
- package/dist/layout/components/sidebar.js.map +1 -1
- package/dist/layout/plugin.d.ts +0 -1
- package/dist/layout/plugin.js.map +1 -1
- package/dist/lib/color.d.ts +9 -0
- package/dist/lib/color.js +48 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/is-truthy.d.ts +2 -0
- package/dist/lib/is-truthy.js +9 -0
- package/dist/lib/is-truthy.js.map +1 -0
- package/dist/lib/use-debounced-callback.d.ts +10 -0
- package/dist/lib/use-debounced-callback.js +34 -0
- package/dist/lib/use-debounced-callback.js.map +1 -0
- package/dist/lib/use-media-query.d.ts +5 -0
- package/dist/lib/use-media-query.js +26 -0
- package/dist/lib/use-media-query.js.map +1 -0
- package/dist/lib/use-persistent-state.d.ts +13 -0
- package/dist/lib/use-persistent-state.js +62 -0
- package/dist/lib/use-persistent-state.js.map +1 -0
- package/dist/notifications/api.d.ts +6 -6
- package/dist/notifications/api.js.map +1 -1
- package/dist/notifications/components/notifications.js +3 -1
- package/dist/notifications/components/notifications.js.map +1 -1
- package/dist/notifications/plugin.d.ts +0 -1
- package/dist/notifications/plugin.js.map +1 -1
- package/dist/notifications/types.d.ts +2 -10
- package/dist/provider-base.js +1 -1
- package/dist/realtime/build-effects.js +5 -5
- package/dist/realtime/build-effects.js.map +1 -1
- package/dist/realtime/listeners.d.ts +2 -2
- package/dist/realtime/listeners.js +1 -1
- package/dist/realtime/listeners.js.map +1 -1
- package/dist/realtime/subscriptions.d.ts +2 -2
- package/dist/realtime/subscriptions.js.map +1 -1
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js.map +1 -1
- package/dist/remote/components/data-list.js +1 -1
- package/dist/remote/plugin.d.ts +0 -1
- package/dist/remote/plugin.js.map +1 -1
- package/dist/table/components/bulk-bar.js +7 -5
- package/dist/table/components/bulk-bar.js.map +1 -1
- package/dist/table/components/cells/badge-cell.js +4 -1
- package/dist/table/components/cells/badge-cell.js.map +1 -1
- package/dist/table/components/cells/boolean-cell.js +4 -4
- package/dist/table/components/cells/boolean-cell.js.map +1 -1
- package/dist/table/components/cells/copyable-cell.d.ts +9 -0
- package/dist/table/components/cells/copyable-cell.js +17 -0
- package/dist/table/components/cells/copyable-cell.js.map +1 -0
- package/dist/table/components/cells/icon-cell.js +5 -2
- package/dist/table/components/cells/icon-cell.js.map +1 -1
- package/dist/table/components/cells/image-cell.js +8 -9
- package/dist/table/components/cells/image-cell.js.map +1 -1
- package/dist/table/components/cells/money-cell.js +7 -10
- package/dist/table/components/cells/money-cell.js.map +1 -1
- package/dist/table/components/cells/number-cell.js +14 -19
- package/dist/table/components/cells/number-cell.js.map +1 -1
- package/dist/table/components/cells/numeric-cell.d.ts +10 -0
- package/dist/table/components/cells/numeric-cell.js +25 -0
- package/dist/table/components/cells/numeric-cell.js.map +1 -0
- package/dist/table/components/cells/stack-cell.js +1 -1
- package/dist/table/components/cells/text-cell.js +8 -6
- package/dist/table/components/cells/text-cell.js.map +1 -1
- package/dist/table/components/column-filter-control.js +25 -32
- package/dist/table/components/column-filter-control.js.map +1 -1
- package/dist/table/components/column-header.d.ts +3 -3
- package/dist/table/components/column-header.js +5 -3
- package/dist/table/components/column-header.js.map +1 -1
- package/dist/table/components/column-visibility-menu.js +6 -11
- package/dist/table/components/column-visibility-menu.js.map +1 -1
- package/dist/table/components/filter-bar.d.ts +2 -2
- package/dist/table/components/filter-bar.js +13 -22
- package/dist/table/components/filter-bar.js.map +1 -1
- package/dist/table/components/filter-controls.d.ts +2 -1
- package/dist/table/components/filter-controls.js +37 -38
- package/dist/table/components/filter-controls.js.map +1 -1
- package/dist/table/components/filter-value-input.d.ts +0 -1
- package/dist/table/components/filter-value-input.js +21 -20
- package/dist/table/components/filter-value-input.js.map +1 -1
- package/dist/table/components/pagination.js +16 -20
- package/dist/table/components/pagination.js.map +1 -1
- package/dist/table/components/sort-bar.d.ts +3 -3
- package/dist/table/components/sort-bar.js +11 -12
- package/dist/table/components/sort-bar.js.map +1 -1
- package/dist/table/components/table-cell.js.map +1 -1
- package/dist/table/components/table-search.d.ts +9 -0
- package/dist/table/components/table-search.js +72 -0
- package/dist/table/components/table-search.js.map +1 -0
- package/dist/table/components/table.js +73 -21
- package/dist/table/components/table.js.map +1 -1
- package/dist/table/hooks/use-column-visibility.js +29 -58
- package/dist/table/hooks/use-column-visibility.js.map +1 -1
- package/dist/table/hooks/use-expanded-rows.d.ts +8 -0
- package/dist/table/hooks/use-expanded-rows.js +24 -0
- package/dist/table/hooks/use-expanded-rows.js.map +1 -0
- package/dist/table/hooks/use-table.d.ts +5 -3
- package/dist/table/hooks/use-table.js +59 -55
- package/dist/table/hooks/use-table.js.map +1 -1
- package/dist/table/lib/bulk.d.ts +2 -0
- package/dist/table/lib/bulk.js +3 -1
- package/dist/table/lib/bulk.js.map +1 -1
- package/dist/table/lib/payload.d.ts +3 -2
- package/dist/table/lib/payload.js +16 -10
- package/dist/table/lib/payload.js.map +1 -1
- package/dist/table/lib/query.d.ts +5 -5
- package/dist/table/lib/query.js +21 -19
- package/dist/table/lib/query.js.map +1 -1
- package/dist/table/plugin.d.ts +0 -1
- package/dist/table/plugin.js.map +1 -1
- package/dist/table/types.d.ts +5 -81
- package/dist/toast/callout.js +2 -4
- package/dist/toast/callout.js.map +1 -1
- package/dist/toast/index.d.ts +1 -1
- package/dist/toast/index.js +2 -2
- package/dist/toast/toast.d.ts +1 -7
- package/dist/toast/toast.js +3 -15
- package/dist/toast/toast.js.map +1 -1
- package/dist/toast/toaster.js.map +1 -1
- package/dist/types/generated.d.ts +444 -225
- package/dist/types/index.d.ts +1 -1
- package/dist/ui/affix-group.d.ts +2 -1
- package/dist/ui/affix-group.js +9 -7
- package/dist/ui/affix-group.js.map +1 -1
- package/dist/ui/avatar.d.ts +3 -0
- package/dist/ui/avatar.js +52 -0
- package/dist/ui/avatar.js.map +1 -0
- package/dist/ui/button.d.ts +5 -3
- package/dist/ui/button.js +10 -3
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/chart-view.js +155 -28
- package/dist/ui/chart-view.js.map +1 -1
- package/dist/ui/click-behavior.d.ts +4 -4
- package/dist/ui/click-behavior.js.map +1 -1
- package/dist/ui/collapsible.js +3 -15
- package/dist/ui/collapsible.js.map +1 -1
- package/dist/ui/color-picker.d.ts +8 -0
- package/dist/ui/color-picker.js +59 -0
- package/dist/ui/color-picker.js.map +1 -0
- package/dist/ui/combobox.d.ts +11 -2
- package/dist/ui/combobox.js +57 -11
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/confirm-dialog.js +1 -1
- package/dist/ui/confirm-dialog.js.map +1 -1
- package/dist/ui/control.d.ts +1 -1
- package/dist/ui/copyable-text.d.ts +7 -0
- package/dist/ui/copyable-text.js +21 -13
- package/dist/ui/copyable-text.js.map +1 -1
- package/dist/ui/dialog.d.ts +6 -2
- package/dist/ui/dialog.js +29 -5
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/fragment.js +1 -1
- package/dist/ui/fragment.js.map +1 -1
- package/dist/ui/heading.js +39 -21
- package/dist/ui/heading.js.map +1 -1
- package/dist/ui/icon-button.d.ts +22 -0
- package/dist/ui/icon-button.js +51 -0
- package/dist/ui/icon-button.js.map +1 -0
- package/dist/ui/icon.js +10 -11
- package/dist/ui/icon.js.map +1 -1
- package/dist/ui/image-preview.d.ts +12 -0
- package/dist/ui/image-preview.js +65 -0
- package/dist/ui/image-preview.js.map +1 -0
- package/dist/ui/image.d.ts +3 -0
- package/dist/ui/image.js +21 -0
- package/dist/ui/image.js.map +1 -0
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.js +4 -3
- package/dist/ui/info-tooltip.js +1 -1
- package/dist/ui/info-tooltip.js.map +1 -1
- package/dist/ui/input.d.ts +3 -1
- package/dist/ui/input.js +2 -2
- package/dist/ui/input.js.map +1 -1
- package/dist/ui/modal.js +17 -4
- package/dist/ui/modal.js.map +1 -1
- package/dist/ui/native-select.d.ts +10 -0
- package/dist/ui/native-select.js +23 -0
- package/dist/ui/native-select.js.map +1 -0
- package/dist/ui/pill.d.ts +2 -0
- package/dist/ui/pill.js +10 -0
- package/dist/ui/pill.js.map +1 -0
- package/dist/ui/plugin.js +11 -1
- package/dist/ui/plugin.js.map +1 -1
- package/dist/ui/progress.d.ts +3 -0
- package/dist/ui/progress.js +114 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/section.js +2 -15
- package/dist/ui/section.js.map +1 -1
- package/dist/ui/segmented-pills.js +2 -1
- package/dist/ui/segmented-pills.js.map +1 -1
- package/dist/ui/separator.d.ts +3 -0
- package/dist/ui/separator.js +15 -0
- package/dist/ui/separator.js.map +1 -0
- package/dist/ui/tabs.js +12 -10
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/text.js +5 -10
- package/dist/ui/text.js.map +1 -1
- package/dist/ui/tree-context.d.ts +33 -0
- package/dist/ui/tree-context.js +135 -0
- package/dist/ui/tree-context.js.map +1 -0
- package/dist/ui/tree.d.ts +19 -0
- package/dist/ui/tree.js +187 -0
- package/dist/ui/tree.js.map +1 -0
- package/dist/ui/use-collapsible-state.d.ts +7 -0
- package/dist/ui/use-collapsible-state.js +21 -0
- package/dist/ui/use-collapsible-state.js.map +1 -0
- package/dist-standalone/chunks/chart-view-C6R5iPra.js +36 -0
- package/dist-standalone/chunks/date-picker-field-DhhEB-Mh.js +1 -0
- package/dist-standalone/chunks/jsx-runtime-Drll_-DD.js +1 -0
- package/dist-standalone/chunks/locale-CXqr7LPR.js +1 -0
- package/dist-standalone/chunks/page-props-DOfg30l5.js +1 -0
- package/dist-standalone/chunks/rich-editor-field-DORXTHPw.js +154 -0
- package/dist-standalone/chunks/{subscriptions-B3UuDBmx.js → subscriptions-B_ISgA7V.js} +1 -1
- package/dist-standalone/chunks/use-effect-dispatcher-D1wPPwHj.js +97 -0
- package/dist-standalone/lattice.css +1 -1
- package/dist-standalone/lattice.js +15 -15
- package/dist-standalone/manifest.json +11 -9
- package/dist-standalone/sprite.svg +1 -1
- package/package.json +6 -1
- package/resources/icons/code-xml.svg +17 -0
- package/resources/icons/heading.svg +17 -0
- package/dist/clipboard/index.js.map +0 -1
- package/dist/events/event-names.js.map +0 -1
- package/dist/table/components/table-action-node.d.ts +0 -4
- package/dist/table/components/table-action-node.js +0 -24
- package/dist/table/components/table-action-node.js.map +0 -1
- package/dist-standalone/chunks/chart-view-CK5fKIkR.js +0 -36
- package/dist-standalone/chunks/date-picker-field-BlCPFhZd.js +0 -1
- package/dist-standalone/chunks/jsx-runtime-CJ_btYLc.js +0 -1
- package/dist-standalone/chunks/rich-editor-field-DH5dS5c1.js +0 -154
- package/dist-standalone/chunks/use-effect-dispatcher-k98Nzf9M.js +0 -97
- /package/dist/{events → core}/event-names.d.ts +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { usePersistentState } from "../lib/use-persistent-state.js";
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
|
3
|
+
var TreeContext = createContext({
|
|
4
|
+
activate: () => {},
|
|
5
|
+
activeId: null,
|
|
6
|
+
expanded: /* @__PURE__ */ new Set(),
|
|
7
|
+
focus: () => {},
|
|
8
|
+
focusedId: null,
|
|
9
|
+
moveFocus: () => {},
|
|
10
|
+
register: () => {},
|
|
11
|
+
toggle: () => {},
|
|
12
|
+
typeAhead: () => {},
|
|
13
|
+
unregister: () => {}
|
|
14
|
+
});
|
|
15
|
+
function useTreeContext() {
|
|
16
|
+
return useContext(TreeContext);
|
|
17
|
+
}
|
|
18
|
+
function parseExpanded(raw) {
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
if (!Array.isArray(parsed)) throw new Error("expected an array of ids");
|
|
21
|
+
return new Set(parsed.filter((id) => typeof id === "string"));
|
|
22
|
+
}
|
|
23
|
+
function visibleOrder(registry) {
|
|
24
|
+
return [...registry.values()].sort((a, b) => a.orderPath.localeCompare(b.orderPath));
|
|
25
|
+
}
|
|
26
|
+
var TYPEAHEAD_IDLE_MS = 800;
|
|
27
|
+
function useTreeState({ activeId: initialActiveId, defaultExpanded, nodes, rememberState, storageKey }) {
|
|
28
|
+
const [expanded, setExpanded] = usePersistentState(storageKey, () => new Set(defaultExpanded), {
|
|
29
|
+
enabled: rememberState,
|
|
30
|
+
parse: parseExpanded,
|
|
31
|
+
serialize: (value) => JSON.stringify([...value])
|
|
32
|
+
});
|
|
33
|
+
const [activeId, setActiveId] = useState(initialActiveId);
|
|
34
|
+
const [focusedId, setFocusedId] = useState(() => nodes[0]?.id ?? null);
|
|
35
|
+
const registryRef = useRef(/* @__PURE__ */ new Map());
|
|
36
|
+
const typeAheadRef = useRef({
|
|
37
|
+
text: "",
|
|
38
|
+
timestamp: 0
|
|
39
|
+
});
|
|
40
|
+
const toggle = useCallback((id) => {
|
|
41
|
+
setExpanded((current) => {
|
|
42
|
+
const next = new Set(current);
|
|
43
|
+
if (next.has(id)) next.delete(id);
|
|
44
|
+
else next.add(id);
|
|
45
|
+
return next;
|
|
46
|
+
});
|
|
47
|
+
}, [setExpanded]);
|
|
48
|
+
const activate = useCallback((id) => setActiveId(id), []);
|
|
49
|
+
const focus = useCallback((id) => {
|
|
50
|
+
setFocusedId(id);
|
|
51
|
+
[...registryRef.current.values()].find((candidate) => candidate.id === id)?.ref.current?.focus();
|
|
52
|
+
}, []);
|
|
53
|
+
const register = useCallback((entry) => {
|
|
54
|
+
registryRef.current.set(entry.path, entry);
|
|
55
|
+
}, []);
|
|
56
|
+
const unregister = useCallback((path) => {
|
|
57
|
+
registryRef.current.delete(path);
|
|
58
|
+
}, []);
|
|
59
|
+
const moveFocus = useCallback((fromId, direction) => {
|
|
60
|
+
const order = visibleOrder(registryRef.current);
|
|
61
|
+
if (order.length === 0) return;
|
|
62
|
+
const index = order.findIndex((entry) => entry.id === fromId);
|
|
63
|
+
const current = index === -1 ? void 0 : order[index];
|
|
64
|
+
let target;
|
|
65
|
+
switch (direction) {
|
|
66
|
+
case "next":
|
|
67
|
+
target = index === -1 ? void 0 : order[index + 1];
|
|
68
|
+
break;
|
|
69
|
+
case "prev":
|
|
70
|
+
target = index === -1 ? void 0 : order[index - 1];
|
|
71
|
+
break;
|
|
72
|
+
case "first":
|
|
73
|
+
target = order[0];
|
|
74
|
+
break;
|
|
75
|
+
case "last":
|
|
76
|
+
target = order[order.length - 1];
|
|
77
|
+
break;
|
|
78
|
+
case "parent":
|
|
79
|
+
target = current ? order.find((entry) => entry.path === current.parentPath) : void 0;
|
|
80
|
+
break;
|
|
81
|
+
case "firstChild":
|
|
82
|
+
target = current ? order.find((entry) => entry.parentPath === current.path) : void 0;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
if (target) focus(target.id);
|
|
86
|
+
}, [focus]);
|
|
87
|
+
const typeAhead = useCallback((fromId, character) => {
|
|
88
|
+
const order = visibleOrder(registryRef.current);
|
|
89
|
+
if (order.length === 0) return;
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const buffer = typeAheadRef.current;
|
|
92
|
+
const text = now - buffer.timestamp > TYPEAHEAD_IDLE_MS ? character : buffer.text + character;
|
|
93
|
+
typeAheadRef.current = {
|
|
94
|
+
text,
|
|
95
|
+
timestamp: now
|
|
96
|
+
};
|
|
97
|
+
const needle = text.toLowerCase();
|
|
98
|
+
const startIndex = order.findIndex((entry) => entry.id === fromId);
|
|
99
|
+
const start = startIndex === -1 ? 0 : startIndex;
|
|
100
|
+
for (let offset = 1; offset <= order.length; offset++) {
|
|
101
|
+
const candidate = order[(start + offset) % order.length];
|
|
102
|
+
if (candidate.label.toLowerCase().startsWith(needle)) {
|
|
103
|
+
focus(candidate.id);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, [focus]);
|
|
108
|
+
return useMemo(() => ({
|
|
109
|
+
activate,
|
|
110
|
+
activeId,
|
|
111
|
+
expanded,
|
|
112
|
+
focus,
|
|
113
|
+
focusedId,
|
|
114
|
+
moveFocus,
|
|
115
|
+
register,
|
|
116
|
+
toggle,
|
|
117
|
+
typeAhead,
|
|
118
|
+
unregister
|
|
119
|
+
}), [
|
|
120
|
+
activate,
|
|
121
|
+
activeId,
|
|
122
|
+
expanded,
|
|
123
|
+
focus,
|
|
124
|
+
focusedId,
|
|
125
|
+
moveFocus,
|
|
126
|
+
register,
|
|
127
|
+
toggle,
|
|
128
|
+
typeAhead,
|
|
129
|
+
unregister
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { TreeContext, useTreeContext, useTreeState };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=tree-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-context.js","names":[],"sources":["../../resources/js/ui/tree-context.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useMemo, useRef, useState } from \"react\";\nimport type { RefObject } from \"react\";\nimport { usePersistentState } from \"@lattice-php/lattice/lib/use-persistent-state\";\n\nexport type TreeItemRegistration = {\n id: string;\n label: string;\n orderPath: string;\n parentPath: string | null;\n path: string;\n ref: RefObject<HTMLLIElement | null>;\n};\n\nexport type TreeFocusDirection = \"first\" | \"firstChild\" | \"last\" | \"next\" | \"parent\" | \"prev\";\n\nexport type TreeContextValue = {\n activate: (id: string) => void;\n activeId: string | null;\n expanded: Set<string>;\n focus: (id: string) => void;\n focusedId: string | null;\n moveFocus: (fromId: string, direction: TreeFocusDirection) => void;\n register: (entry: TreeItemRegistration) => void;\n toggle: (id: string) => void;\n typeAhead: (fromId: string, character: string) => void;\n unregister: (path: string) => void;\n};\n\nconst defaultTreeContext: TreeContextValue = {\n activate: () => {},\n activeId: null,\n expanded: new Set(),\n focus: () => {},\n focusedId: null,\n moveFocus: () => {},\n register: () => {},\n toggle: () => {},\n typeAhead: () => {},\n unregister: () => {},\n};\n\nexport const TreeContext = createContext<TreeContextValue>(defaultTreeContext);\n\nexport function useTreeContext(): TreeContextValue {\n return useContext(TreeContext);\n}\n\nfunction parseExpanded(raw: string): Set<string> {\n const parsed: unknown = JSON.parse(raw);\n\n if (!Array.isArray(parsed)) {\n throw new Error(\"expected an array of ids\");\n }\n\n return new Set(parsed.filter((id): id is string => typeof id === \"string\"));\n}\n\nfunction visibleOrder(registry: Map<string, TreeItemRegistration>): TreeItemRegistration[] {\n return [...registry.values()].sort((a, b) => a.orderPath.localeCompare(b.orderPath));\n}\n\nconst TYPEAHEAD_IDLE_MS = 800;\n\nexport function useTreeState({\n activeId: initialActiveId,\n defaultExpanded,\n nodes,\n rememberState,\n storageKey,\n}: {\n activeId: string | null;\n defaultExpanded: string[];\n nodes: Array<{ id: string }>;\n rememberState: boolean;\n storageKey: string;\n}): TreeContextValue {\n const [expanded, setExpanded] = usePersistentState<Set<string>>(\n storageKey,\n () => new Set(defaultExpanded),\n {\n enabled: rememberState,\n parse: parseExpanded,\n serialize: (value) => JSON.stringify([...value]),\n },\n );\n const [activeId, setActiveId] = useState<string | null>(initialActiveId);\n const [focusedId, setFocusedId] = useState<string | null>(() => nodes[0]?.id ?? null);\n const registryRef = useRef<Map<string, TreeItemRegistration>>(new Map());\n const typeAheadRef = useRef<{ text: string; timestamp: number }>({ text: \"\", timestamp: 0 });\n\n const toggle = useCallback(\n (id: string) => {\n setExpanded((current) => {\n const next = new Set(current);\n\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n\n return next;\n });\n },\n [setExpanded],\n );\n\n const activate = useCallback((id: string) => setActiveId(id), []);\n\n const focus = useCallback((id: string) => {\n setFocusedId(id);\n const entry = [...registryRef.current.values()].find((candidate) => candidate.id === id);\n entry?.ref.current?.focus();\n }, []);\n\n const register = useCallback((entry: TreeItemRegistration) => {\n registryRef.current.set(entry.path, entry);\n }, []);\n\n const unregister = useCallback((path: string) => {\n registryRef.current.delete(path);\n }, []);\n\n const moveFocus = useCallback(\n (fromId: string, direction: TreeFocusDirection) => {\n const order = visibleOrder(registryRef.current);\n\n if (order.length === 0) {\n return;\n }\n\n const index = order.findIndex((entry) => entry.id === fromId);\n const current = index === -1 ? undefined : order[index];\n\n let target: TreeItemRegistration | undefined;\n\n switch (direction) {\n case \"next\":\n target = index === -1 ? undefined : order[index + 1];\n break;\n case \"prev\":\n target = index === -1 ? undefined : order[index - 1];\n break;\n case \"first\":\n target = order[0];\n break;\n case \"last\":\n target = order[order.length - 1];\n break;\n case \"parent\":\n target = current ? order.find((entry) => entry.path === current.parentPath) : undefined;\n break;\n case \"firstChild\":\n target = current ? order.find((entry) => entry.parentPath === current.path) : undefined;\n break;\n }\n\n if (target) {\n focus(target.id);\n }\n },\n [focus],\n );\n\n const typeAhead = useCallback(\n (fromId: string, character: string) => {\n const order = visibleOrder(registryRef.current);\n\n if (order.length === 0) {\n return;\n }\n\n const now = Date.now();\n const buffer = typeAheadRef.current;\n const text = now - buffer.timestamp > TYPEAHEAD_IDLE_MS ? character : buffer.text + character;\n typeAheadRef.current = { text, timestamp: now };\n\n const needle = text.toLowerCase();\n const startIndex = order.findIndex((entry) => entry.id === fromId);\n const start = startIndex === -1 ? 0 : startIndex;\n\n for (let offset = 1; offset <= order.length; offset++) {\n const candidate = order[(start + offset) % order.length];\n\n if (candidate.label.toLowerCase().startsWith(needle)) {\n focus(candidate.id);\n return;\n }\n }\n },\n [focus],\n );\n\n return useMemo(\n () => ({\n activate,\n activeId,\n expanded,\n focus,\n focusedId,\n moveFocus,\n register,\n toggle,\n typeAhead,\n unregister,\n }),\n [\n activate,\n activeId,\n expanded,\n focus,\n focusedId,\n moveFocus,\n register,\n toggle,\n typeAhead,\n unregister,\n ],\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RendererComponent } from '../core/types.js';
|
|
2
|
+
import { Tree as TreeProps, TreeNode as GeneratedTreeNode } from '../types/generated.js';
|
|
3
|
+
/**
|
|
4
|
+
* The actual sparse wire shape a tree node serializes as (see `TreeNode::jsonSerialize()`):
|
|
5
|
+
* every optional/falsy field is omitted rather than sent as `null`/`false`, unlike the
|
|
6
|
+
* generated `TreeNode`, which reflects every declared PHP property as required.
|
|
7
|
+
*/
|
|
8
|
+
export type TreeNodeData = Pick<GeneratedTreeNode, "id" | "label"> & Partial<Omit<GeneratedTreeNode, "children" | "id" | "label">> & {
|
|
9
|
+
children?: TreeNodeData[];
|
|
10
|
+
};
|
|
11
|
+
declare module '../core/types' {
|
|
12
|
+
interface ComponentProps {
|
|
13
|
+
tree: TreeProps & {
|
|
14
|
+
nodes: TreeNodeData[];
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
declare const TreeComponent: RendererComponent<"tree">;
|
|
19
|
+
export default TreeComponent;
|
package/dist/ui/tree.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { Icon } from "../icons/sprite.js";
|
|
3
|
+
import { IconRenderer } from "../icons/icon-renderer.js";
|
|
4
|
+
import { Renderer } from "../core/renderer.js";
|
|
5
|
+
import { useT } from "../i18n/instance.js";
|
|
6
|
+
import { nodeIdentity } from "../core/test-id.js";
|
|
7
|
+
import { Badge } from "./badge.js";
|
|
8
|
+
import { TextLink } from "./link.js";
|
|
9
|
+
import { TreeContext, useTreeContext, useTreeState } from "./tree-context.js";
|
|
10
|
+
import { router } from "@inertiajs/react";
|
|
11
|
+
import { useEffect, useRef } from "react";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
//#region resources/js/ui/tree.tsx
|
|
14
|
+
function hasExpandableChildren(node) {
|
|
15
|
+
return Boolean(node.children?.length) || node.hasChildren === true;
|
|
16
|
+
}
|
|
17
|
+
var ORDER_PATH_SEGMENT_WIDTH = 6;
|
|
18
|
+
function orderPathSegment(index) {
|
|
19
|
+
return String(index).padStart(ORDER_PATH_SEGMENT_WIDTH, "0");
|
|
20
|
+
}
|
|
21
|
+
function TreeItem({ depth, node, orderPath, parentPath, siblingCount, siblingIndex }) {
|
|
22
|
+
const { activate, activeId, expanded, focusedId, moveFocus, register, toggle, typeAhead, unregister } = useTreeContext();
|
|
23
|
+
const { t } = useT("lattice");
|
|
24
|
+
const ref = useRef(null);
|
|
25
|
+
const path = parentPath ? `${parentPath}/${node.id}` : node.id;
|
|
26
|
+
const isExpanded = expanded.has(node.id);
|
|
27
|
+
const isActive = activeId === node.id;
|
|
28
|
+
const isFocused = focusedId === node.id;
|
|
29
|
+
const isDisabled = node.disabled === true;
|
|
30
|
+
const expandable = hasExpandableChildren(node);
|
|
31
|
+
const actionsRef = useRef(null);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
register({
|
|
34
|
+
id: node.id,
|
|
35
|
+
label: node.label,
|
|
36
|
+
orderPath,
|
|
37
|
+
parentPath,
|
|
38
|
+
path,
|
|
39
|
+
ref
|
|
40
|
+
});
|
|
41
|
+
return () => unregister(path);
|
|
42
|
+
}, [
|
|
43
|
+
node.id,
|
|
44
|
+
node.label,
|
|
45
|
+
orderPath,
|
|
46
|
+
parentPath,
|
|
47
|
+
path,
|
|
48
|
+
register,
|
|
49
|
+
unregister
|
|
50
|
+
]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const container = actionsRef.current;
|
|
53
|
+
if (!container) return;
|
|
54
|
+
container.querySelectorAll("button, a[href], [tabindex]").forEach((control) => {
|
|
55
|
+
control.tabIndex = -1;
|
|
56
|
+
});
|
|
57
|
+
}, [node.actions]);
|
|
58
|
+
function onKeyDown(event) {
|
|
59
|
+
if (event.target !== event.currentTarget) return;
|
|
60
|
+
switch (event.key) {
|
|
61
|
+
case "ArrowDown":
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
moveFocus(node.id, "next");
|
|
64
|
+
return;
|
|
65
|
+
case "ArrowUp":
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
moveFocus(node.id, "prev");
|
|
68
|
+
return;
|
|
69
|
+
case "ArrowRight":
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
if (expandable && !isExpanded) toggle(node.id);
|
|
72
|
+
else if (expandable) moveFocus(node.id, "firstChild");
|
|
73
|
+
return;
|
|
74
|
+
case "ArrowLeft":
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
if (expandable && isExpanded) toggle(node.id);
|
|
77
|
+
else moveFocus(node.id, "parent");
|
|
78
|
+
return;
|
|
79
|
+
case "Home":
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
moveFocus(node.id, "first");
|
|
82
|
+
return;
|
|
83
|
+
case "End":
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
moveFocus(node.id, "last");
|
|
86
|
+
return;
|
|
87
|
+
case "Enter":
|
|
88
|
+
case " ":
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
if (isDisabled) return;
|
|
91
|
+
if (node.href) router.visit(node.href);
|
|
92
|
+
else if (node.actions) {
|
|
93
|
+
actionsRef.current?.querySelector("button")?.click();
|
|
94
|
+
ref.current?.focus();
|
|
95
|
+
} else activate(node.id);
|
|
96
|
+
return;
|
|
97
|
+
default: if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) typeAhead(node.id, event.key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
101
|
+
"aria-disabled": isDisabled,
|
|
102
|
+
"aria-expanded": expandable ? isExpanded : void 0,
|
|
103
|
+
"aria-level": depth,
|
|
104
|
+
"aria-posinset": siblingIndex,
|
|
105
|
+
"aria-selected": isActive,
|
|
106
|
+
"aria-setsize": siblingCount,
|
|
107
|
+
"data-test": `tree-node-${node.id}`,
|
|
108
|
+
onKeyDown,
|
|
109
|
+
ref,
|
|
110
|
+
role: "treeitem",
|
|
111
|
+
tabIndex: isFocused ? 0 : -1,
|
|
112
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
113
|
+
className: cn("flex items-center gap-2 rounded-lt-sm px-2 py-1.5 text-sm text-lt-fg", isActive && "bg-lt-muted font-medium", isDisabled && "pointer-events-none opacity-50"),
|
|
114
|
+
children: [
|
|
115
|
+
expandable ? /* @__PURE__ */ jsx("button", {
|
|
116
|
+
"aria-label": isExpanded ? t("common.tree.collapse", "Collapse {{label}}", { label: node.label }) : t("common.tree.expand", "Expand {{label}}", { label: node.label }),
|
|
117
|
+
"data-test": `tree-node-${node.id}-toggle`,
|
|
118
|
+
onClick: () => toggle(node.id),
|
|
119
|
+
tabIndex: -1,
|
|
120
|
+
type: "button",
|
|
121
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
122
|
+
className: cn("size-lt-icon-md shrink-0 transition-transform", isExpanded && "rotate-90"),
|
|
123
|
+
name: "chevron-right"
|
|
124
|
+
})
|
|
125
|
+
}) : null,
|
|
126
|
+
node.icon ? /* @__PURE__ */ jsx(IconRenderer, {
|
|
127
|
+
className: "size-lt-icon-md shrink-0",
|
|
128
|
+
icon: node.icon
|
|
129
|
+
}) : null,
|
|
130
|
+
node.href && !isDisabled ? /* @__PURE__ */ jsx(TextLink, {
|
|
131
|
+
href: node.href,
|
|
132
|
+
tabIndex: -1,
|
|
133
|
+
children: node.label
|
|
134
|
+
}) : /* @__PURE__ */ jsx("span", { children: node.label }),
|
|
135
|
+
node.badge ? /* @__PURE__ */ jsx(Badge, {
|
|
136
|
+
variant: "secondary",
|
|
137
|
+
children: node.badge
|
|
138
|
+
}) : null,
|
|
139
|
+
node.actions ? /* @__PURE__ */ jsx("span", {
|
|
140
|
+
className: "ml-auto",
|
|
141
|
+
ref: actionsRef,
|
|
142
|
+
children: /* @__PURE__ */ jsx(Renderer, { nodes: [node.actions] })
|
|
143
|
+
}) : null
|
|
144
|
+
]
|
|
145
|
+
}), expandable && isExpanded && node.children && node.children.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
146
|
+
className: "pl-6",
|
|
147
|
+
role: "group",
|
|
148
|
+
children: node.children.map((child, index) => /* @__PURE__ */ jsx(TreeItem, {
|
|
149
|
+
depth: depth + 1,
|
|
150
|
+
node: child,
|
|
151
|
+
orderPath: `${orderPath}.${orderPathSegment(index)}`,
|
|
152
|
+
parentPath: path,
|
|
153
|
+
siblingCount: node.children?.length ?? 1,
|
|
154
|
+
siblingIndex: index + 1
|
|
155
|
+
}, child.id))
|
|
156
|
+
}) : null]
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
var TreeComponent = ({ node }) => {
|
|
160
|
+
const identity = nodeIdentity(node);
|
|
161
|
+
const value = useTreeState({
|
|
162
|
+
activeId: node.props.activeId,
|
|
163
|
+
defaultExpanded: node.props.defaultExpanded,
|
|
164
|
+
nodes: node.props.nodes,
|
|
165
|
+
rememberState: node.props.rememberState,
|
|
166
|
+
storageKey: `lattice:tree:${identity ?? "default"}`
|
|
167
|
+
});
|
|
168
|
+
return /* @__PURE__ */ jsx(TreeContext.Provider, {
|
|
169
|
+
value,
|
|
170
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
171
|
+
"data-lattice-component": identity,
|
|
172
|
+
role: "tree",
|
|
173
|
+
children: node.props.nodes.map((child, index) => /* @__PURE__ */ jsx(TreeItem, {
|
|
174
|
+
depth: 1,
|
|
175
|
+
node: child,
|
|
176
|
+
orderPath: orderPathSegment(index),
|
|
177
|
+
parentPath: null,
|
|
178
|
+
siblingCount: node.props.nodes.length,
|
|
179
|
+
siblingIndex: index + 1
|
|
180
|
+
}, child.id))
|
|
181
|
+
})
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
//#endregion
|
|
185
|
+
export { TreeComponent as default };
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.js","names":[],"sources":["../../resources/js/ui/tree.tsx"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport { useEffect, useRef } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { Icon, IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type {\n Tree as TreeProps,\n TreeNode as GeneratedTreeNode,\n} from \"@lattice-php/lattice/types/generated\";\nimport { Badge } from \"./badge\";\nimport { TextLink } from \"./link\";\nimport { TreeContext, useTreeContext, useTreeState } from \"./tree-context\";\n\n/**\n * The actual sparse wire shape a tree node serializes as (see `TreeNode::jsonSerialize()`):\n * every optional/falsy field is omitted rather than sent as `null`/`false`, unlike the\n * generated `TreeNode`, which reflects every declared PHP property as required.\n */\nexport type TreeNodeData = Pick<GeneratedTreeNode, \"id\" | \"label\"> &\n Partial<Omit<GeneratedTreeNode, \"children\" | \"id\" | \"label\">> & {\n children?: TreeNodeData[];\n };\n\ndeclare module \"@lattice-php/lattice/core/types\" {\n interface ComponentProps {\n tree: TreeProps & { nodes: TreeNodeData[] };\n }\n}\n\nfunction hasExpandableChildren(node: TreeNodeData): boolean {\n return Boolean(node.children?.length) || node.hasChildren === true;\n}\n\nconst ORDER_PATH_SEGMENT_WIDTH = 6;\n\nfunction orderPathSegment(index: number): string {\n return String(index).padStart(ORDER_PATH_SEGMENT_WIDTH, \"0\");\n}\n\nfunction TreeItem({\n depth,\n node,\n orderPath,\n parentPath,\n siblingCount,\n siblingIndex,\n}: {\n depth: number;\n node: TreeNodeData;\n orderPath: string;\n parentPath: string | null;\n siblingCount: number;\n siblingIndex: number;\n}) {\n const {\n activate,\n activeId,\n expanded,\n focusedId,\n moveFocus,\n register,\n toggle,\n typeAhead,\n unregister,\n } = useTreeContext();\n const { t } = useT(\"lattice\");\n const ref = useRef<HTMLLIElement>(null);\n const path = parentPath ? `${parentPath}/${node.id}` : node.id;\n const isExpanded = expanded.has(node.id);\n const isActive = activeId === node.id;\n const isFocused = focusedId === node.id;\n const isDisabled = node.disabled === true;\n const expandable = hasExpandableChildren(node);\n const actionsRef = useRef<HTMLSpanElement>(null);\n\n useEffect(() => {\n register({ id: node.id, label: node.label, orderPath, parentPath, path, ref });\n\n return () => unregister(path);\n }, [node.id, node.label, orderPath, parentPath, path, register, unregister]);\n\n useEffect(() => {\n const container = actionsRef.current;\n\n if (!container) {\n return;\n }\n\n container.querySelectorAll<HTMLElement>(\"button, a[href], [tabindex]\").forEach((control) => {\n control.tabIndex = -1;\n });\n }, [node.actions]);\n\n function onKeyDown(event: KeyboardEvent<HTMLLIElement>): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n moveFocus(node.id, \"next\");\n return;\n case \"ArrowUp\":\n event.preventDefault();\n moveFocus(node.id, \"prev\");\n return;\n case \"ArrowRight\":\n event.preventDefault();\n if (expandable && !isExpanded) {\n toggle(node.id);\n } else if (expandable) {\n moveFocus(node.id, \"firstChild\");\n }\n return;\n case \"ArrowLeft\":\n event.preventDefault();\n if (expandable && isExpanded) {\n toggle(node.id);\n } else {\n moveFocus(node.id, \"parent\");\n }\n return;\n case \"Home\":\n event.preventDefault();\n moveFocus(node.id, \"first\");\n return;\n case \"End\":\n event.preventDefault();\n moveFocus(node.id, \"last\");\n return;\n case \"Enter\":\n case \" \":\n event.preventDefault();\n if (isDisabled) {\n return;\n }\n if (node.href) {\n router.visit(node.href);\n } else if (node.actions) {\n actionsRef.current?.querySelector(\"button\")?.click();\n ref.current?.focus();\n } else {\n activate(node.id);\n }\n return;\n default:\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {\n typeAhead(node.id, event.key);\n }\n }\n }\n\n return (\n <li\n aria-disabled={isDisabled}\n aria-expanded={expandable ? isExpanded : undefined}\n aria-level={depth}\n aria-posinset={siblingIndex}\n aria-selected={isActive}\n aria-setsize={siblingCount}\n data-test={`tree-node-${node.id}`}\n onKeyDown={onKeyDown}\n ref={ref}\n role=\"treeitem\"\n tabIndex={isFocused ? 0 : -1}\n >\n <div\n className={cn(\n \"flex items-center gap-2 rounded-lt-sm px-2 py-1.5 text-sm text-lt-fg\",\n isActive && \"bg-lt-muted font-medium\",\n isDisabled && \"pointer-events-none opacity-50\",\n )}\n >\n {expandable ? (\n <button\n aria-label={\n isExpanded\n ? t(\"common.tree.collapse\", \"Collapse {{label}}\", { label: node.label })\n : t(\"common.tree.expand\", \"Expand {{label}}\", { label: node.label })\n }\n data-test={`tree-node-${node.id}-toggle`}\n onClick={() => toggle(node.id)}\n tabIndex={-1}\n type=\"button\"\n >\n <Icon\n className={cn(\n \"size-lt-icon-md shrink-0 transition-transform\",\n isExpanded && \"rotate-90\",\n )}\n name=\"chevron-right\"\n />\n </button>\n ) : null}\n {node.icon ? <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={node.icon} /> : null}\n {node.href && !isDisabled ? (\n <TextLink href={node.href} tabIndex={-1}>\n {node.label}\n </TextLink>\n ) : (\n <span>{node.label}</span>\n )}\n {node.badge ? <Badge variant=\"secondary\">{node.badge}</Badge> : null}\n {node.actions ? (\n <span className=\"ml-auto\" ref={actionsRef}>\n <Renderer nodes={[node.actions]} />\n </span>\n ) : null}\n </div>\n {expandable && isExpanded && node.children && node.children.length > 0 ? (\n <ul className=\"pl-6\" role=\"group\">\n {node.children.map((child, index) => (\n <TreeItem\n depth={depth + 1}\n key={child.id}\n node={child}\n orderPath={`${orderPath}.${orderPathSegment(index)}`}\n parentPath={path}\n siblingCount={node.children?.length ?? 1}\n siblingIndex={index + 1}\n />\n ))}\n </ul>\n ) : null}\n </li>\n );\n}\n\nconst TreeComponent: RendererComponent<\"tree\"> = ({ node }) => {\n const identity = nodeIdentity(node);\n const value = useTreeState({\n activeId: node.props.activeId,\n defaultExpanded: node.props.defaultExpanded,\n nodes: node.props.nodes,\n rememberState: node.props.rememberState,\n storageKey: `lattice:tree:${identity ?? \"default\"}`,\n });\n\n return (\n <TreeContext.Provider value={value}>\n <ul data-lattice-component={identity} role=\"tree\">\n {node.props.nodes.map((child, index) => (\n <TreeItem\n depth={1}\n key={child.id}\n node={child}\n orderPath={orderPathSegment(index)}\n parentPath={null}\n siblingCount={node.props.nodes.length}\n siblingIndex={index + 1}\n />\n ))}\n </ul>\n </TreeContext.Provider>\n );\n};\n\nexport default TreeComponent;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boolean open/collapsed state remembered in `localStorage` as `"true"`/`"false"`.
|
|
3
|
+
* Shared by the section, collapsible, and sidebar chrome. Callers resolve
|
|
4
|
+
* `rememberState` as `props.rememberState !== false`; the wire prop is always a
|
|
5
|
+
* boolean, so the polarity is uniform across all three.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useCollapsibleState(storageKey: string, fallback: boolean, rememberState: boolean): [boolean, () => void];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { usePersistentState } from "../lib/use-persistent-state.js";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
//#region resources/js/ui/use-collapsible-state.ts
|
|
4
|
+
/**
|
|
5
|
+
* Boolean open/collapsed state remembered in `localStorage` as `"true"`/`"false"`.
|
|
6
|
+
* Shared by the section, collapsible, and sidebar chrome. Callers resolve
|
|
7
|
+
* `rememberState` as `props.rememberState !== false`; the wire prop is always a
|
|
8
|
+
* boolean, so the polarity is uniform across all three.
|
|
9
|
+
*/
|
|
10
|
+
function useCollapsibleState(storageKey, fallback, rememberState) {
|
|
11
|
+
const [value, setValue] = usePersistentState(storageKey, fallback, {
|
|
12
|
+
enabled: rememberState,
|
|
13
|
+
parse: (raw) => raw === "true",
|
|
14
|
+
serialize: String
|
|
15
|
+
});
|
|
16
|
+
return [value, useCallback(() => setValue((current) => !current), [setValue])];
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { useCollapsibleState };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=use-collapsible-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-collapsible-state.js","names":[],"sources":["../../resources/js/ui/use-collapsible-state.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { usePersistentState } from \"@lattice-php/lattice/lib/use-persistent-state\";\n\n/**\n * Boolean open/collapsed state remembered in `localStorage` as `\"true\"`/`\"false\"`.\n * Shared by the section, collapsible, and sidebar chrome. Callers resolve\n * `rememberState` as `props.rememberState !== false`; the wire prop is always a\n * boolean, so the polarity is uniform across all three.\n */\nexport function useCollapsibleState(\n storageKey: string,\n fallback: boolean,\n rememberState: boolean,\n): [boolean, () => void] {\n const [value, setValue] = usePersistentState<boolean>(storageKey, fallback, {\n enabled: rememberState,\n parse: (raw) => raw === \"true\",\n serialize: String,\n });\n\n const toggle = useCallback(() => setValue((current) => !current), [setValue]);\n\n return [value, toggle];\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,oBACd,YACA,UACA,eACuB;CACvB,MAAM,CAAC,OAAO,YAAY,mBAA4B,YAAY,UAAU;EAC1E,SAAS;EACT,QAAQ,QAAQ,QAAQ;EACxB,WAAW;CACb,CAAC;CAID,OAAO,CAAC,OAFO,kBAAkB,UAAU,YAAY,CAAC,OAAO,GAAG,CAAC,QAAQ,CAE5D,CAAM;AACvB"}
|