@viniaraujo68/plinth 0.0.0-stage → 0.2.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 (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +361 -2
  3. package/dist/anchoring.d.ts +61 -0
  4. package/dist/anchoring.js +151 -0
  5. package/dist/async-action.svelte.d.ts +37 -0
  6. package/dist/async-action.svelte.js +64 -0
  7. package/dist/attachments/index.d.ts +1 -0
  8. package/dist/attachments/index.js +1 -0
  9. package/dist/attachments/tooltip.d.ts +37 -0
  10. package/dist/attachments/tooltip.js +166 -0
  11. package/dist/components/AsyncButton.svelte +49 -0
  12. package/dist/components/AsyncButton.svelte.d.ts +32 -0
  13. package/dist/components/Combobox.svelte +380 -0
  14. package/dist/components/Combobox.svelte.d.ts +74 -0
  15. package/dist/components/Copyable.svelte +116 -0
  16. package/dist/components/Copyable.svelte.d.ts +37 -0
  17. package/dist/components/DateRangePicker.svelte +266 -0
  18. package/dist/components/DateRangePicker.svelte.d.ts +66 -0
  19. package/dist/components/Dialog.svelte +102 -0
  20. package/dist/components/Dialog.svelte.d.ts +38 -0
  21. package/dist/components/Dropdown.svelte +204 -0
  22. package/dist/components/Dropdown.svelte.d.ts +49 -0
  23. package/dist/components/ErrorDisplay.svelte +59 -0
  24. package/dist/components/ErrorDisplay.svelte.d.ts +24 -0
  25. package/dist/components/ErrorPage.svelte +119 -0
  26. package/dist/components/ErrorPage.svelte.d.ts +52 -0
  27. package/dist/components/JsonInput.svelte +106 -0
  28. package/dist/components/JsonInput.svelte.d.ts +40 -0
  29. package/dist/components/Listbox.svelte +257 -0
  30. package/dist/components/Listbox.svelte.d.ts +78 -0
  31. package/dist/components/LoadingButton.svelte +36 -0
  32. package/dist/components/LoadingButton.svelte.d.ts +22 -0
  33. package/dist/components/Logo.svelte +112 -0
  34. package/dist/components/Logo.svelte.d.ts +29 -0
  35. package/dist/components/Modal.svelte +187 -0
  36. package/dist/components/Modal.svelte.d.ts +62 -0
  37. package/dist/components/MultiSelect.svelte +256 -0
  38. package/dist/components/MultiSelect.svelte.d.ts +89 -0
  39. package/dist/components/OptionPanel.svelte +241 -0
  40. package/dist/components/OptionPanel.svelte.d.ts +51 -0
  41. package/dist/components/Paginator.svelte +205 -0
  42. package/dist/components/Paginator.svelte.d.ts +48 -0
  43. package/dist/components/RefreshButton.svelte +246 -0
  44. package/dist/components/RefreshButton.svelte.d.ts +46 -0
  45. package/dist/components/SegmentedControl.svelte +147 -0
  46. package/dist/components/SegmentedControl.svelte.d.ts +71 -0
  47. package/dist/components/Select.svelte +312 -0
  48. package/dist/components/Select.svelte.d.ts +68 -0
  49. package/dist/components/Skeleton.svelte +97 -0
  50. package/dist/components/Skeleton.svelte.d.ts +32 -0
  51. package/dist/components/Tooltip.svelte +60 -0
  52. package/dist/components/Tooltip.svelte.d.ts +58 -0
  53. package/dist/components/date-range.d.ts +101 -0
  54. package/dist/components/date-range.js +144 -0
  55. package/dist/components/index.d.ts +25 -0
  56. package/dist/components/index.js +37 -0
  57. package/dist/components/json-text.d.ts +22 -0
  58. package/dist/components/json-text.js +22 -0
  59. package/dist/components/pick.d.ts +18 -0
  60. package/dist/components/pick.js +18 -0
  61. package/dist/components/segmented.d.ts +7 -0
  62. package/dist/components/segmented.js +1 -0
  63. package/dist/components/select.d.ts +66 -0
  64. package/dist/components/select.js +90 -0
  65. package/dist/confirm/Confirmer.svelte +194 -0
  66. package/dist/confirm/Confirmer.svelte.d.ts +45 -0
  67. package/dist/confirm/challenge.d.ts +16 -0
  68. package/dist/confirm/challenge.js +16 -0
  69. package/dist/confirm/confirm-manager.svelte.d.ts +91 -0
  70. package/dist/confirm/confirm-manager.svelte.js +149 -0
  71. package/dist/confirm/index.d.ts +3 -0
  72. package/dist/confirm/index.js +3 -0
  73. package/dist/error.d.ts +25 -0
  74. package/dist/error.js +39 -0
  75. package/dist/formatters.d.ts +95 -0
  76. package/dist/formatters.js +146 -0
  77. package/dist/http.d.ts +68 -0
  78. package/dist/http.js +138 -0
  79. package/dist/index.d.ts +10 -0
  80. package/dist/index.js +14 -0
  81. package/dist/routing.svelte.d.ts +169 -0
  82. package/dist/routing.svelte.js +208 -0
  83. package/dist/shell/AppShell.svelte +874 -0
  84. package/dist/shell/AppShell.svelte.d.ts +93 -0
  85. package/dist/shell/Breadcrumbs.svelte +78 -0
  86. package/dist/shell/Breadcrumbs.svelte.d.ts +25 -0
  87. package/dist/shell/crumbs.d.ts +23 -0
  88. package/dist/shell/crumbs.js +1 -0
  89. package/dist/shell/index.d.ts +3 -0
  90. package/dist/shell/index.js +6 -0
  91. package/dist/table/ColumnMenu.svelte +249 -0
  92. package/dist/table/ColumnMenu.svelte.d.ts +47 -0
  93. package/dist/table/DataTable.svelte +643 -0
  94. package/dist/table/DataTable.svelte.d.ts +153 -0
  95. package/dist/table/columns.d.ts +55 -0
  96. package/dist/table/columns.js +153 -0
  97. package/dist/table/index.d.ts +4 -0
  98. package/dist/table/index.js +8 -0
  99. package/dist/table/sort.d.ts +48 -0
  100. package/dist/table/sort.js +139 -0
  101. package/dist/table/types.d.ts +93 -0
  102. package/dist/table/types.js +1 -0
  103. package/dist/theme/ThemeController.svelte +54 -0
  104. package/dist/theme/ThemeController.svelte.d.ts +27 -0
  105. package/dist/theme/ThemeToggle.svelte +119 -0
  106. package/dist/theme/ThemeToggle.svelte.d.ts +52 -0
  107. package/dist/theme/css-color.d.ts +23 -0
  108. package/dist/theme/css-color.js +31 -0
  109. package/dist/theme/index.d.ts +4 -0
  110. package/dist/theme/index.js +5 -0
  111. package/dist/theme/theme.css +410 -0
  112. package/dist/theme/theme.svelte.d.ts +54 -0
  113. package/dist/theme/theme.svelte.js +58 -0
  114. package/dist/toast/Toaster.svelte +257 -0
  115. package/dist/toast/Toaster.svelte.d.ts +35 -0
  116. package/dist/toast/index.d.ts +2 -0
  117. package/dist/toast/index.js +2 -0
  118. package/dist/toast/toast-manager.svelte.d.ts +99 -0
  119. package/dist/toast/toast-manager.svelte.js +176 -0
  120. package/dist/user/ScopedComponent.svelte +47 -0
  121. package/dist/user/ScopedComponent.svelte.d.ts +29 -0
  122. package/dist/user/anonymous.d.ts +21 -0
  123. package/dist/user/anonymous.js +26 -0
  124. package/dist/user/context.d.ts +55 -0
  125. package/dist/user/context.js +13 -0
  126. package/dist/user/index.d.ts +3 -0
  127. package/dist/user/index.js +6 -0
  128. package/package.json +133 -4
@@ -0,0 +1,93 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { ClassValue } from "svelte/elements";
3
+ import { type ResolvedRoute } from "../routing.svelte.js";
4
+ interface Props {
5
+ /** The routed page. The shell owns the scroll container around it. */
6
+ children: Snippet;
7
+ /**
8
+ * The sidebar header, and the bottom sheet's header on narrow screens. Receives the collapse
9
+ * state so a caller can swap a wordmark for a mark instead of letting it be clipped.
10
+ */
11
+ brand?: Snippet<[{
12
+ collapsed: boolean;
13
+ }]>;
14
+ /** Extra sidebar footer content, above the user block. A theme picker usually goes here. */
15
+ footer?: Snippet;
16
+ /**
17
+ * Renders one entry's icon. Without it the shell emits `<span class={meta.icon}>`, which is
18
+ * what an icon font or an Iconify/Tailwind class wants; supply it to render SVG components.
19
+ */
20
+ icon?: Snippet<[ResolvedRoute]>;
21
+ /** Extra classes for the shell root — sizing, mostly. */
22
+ class?: ClassValue;
23
+ /**
24
+ * How many items the bottom bar renders, the trailing "More" item counted among them. Entries
25
+ * that do not fit collapse into the sheet "More" opens — and so does everything the bar has no
26
+ * room for at all: the brand, the footer snippet and the user block. "More" therefore appears
27
+ * with nothing overflowing whenever any of those exist, and costs a nav slot only once the bar
28
+ * is genuinely full: three entries in five slots render three plus "More", five render four
29
+ * plus "More" and put the fifth in the sheet.
30
+ */
31
+ bottomBarSlots?: number;
32
+ /** Where the collapse state is remembered. `null` turns persistence off. */
33
+ storageKey?: string | null;
34
+ /** Whether the sidebar is collapsed to icons. Bindable, so a page can drive it too. */
35
+ collapsed?: boolean;
36
+ /** Accessible name of both navigations. A prop because the library ships no translations. */
37
+ navLabel?: string;
38
+ /** Accessible name of the collapse button. */
39
+ collapseLabel?: string;
40
+ /** Label of the bottom bar's overflow item, and heading of the sheet it opens. */
41
+ moreLabel?: string;
42
+ /** Accessible name of the sheet's close button. */
43
+ closeLabel?: string;
44
+ /** Label of the sign-out control, which only appears when the user context can sign out. */
45
+ logoutLabel?: string;
46
+ }
47
+ /**
48
+ * The application shell: a collapsible sidebar on wide viewports, a fixed bottom bar on narrow ones,
49
+ * both fed by the same `RoutingContext`. The route tree is declared once, by the filesystem and the
50
+ * routing config; nothing here re-states it.
51
+ *
52
+ * Which form shows is decided by a **container query on the shell root**, not by JavaScript. There is
53
+ * no resize listener and no `matchMedia`, so nothing flashes the wrong layout on the first paint —
54
+ * and because the root is its own container, the shell can be dropped into a narrow box to render its
55
+ * mobile form on a desktop, which is exactly what the showcase does.
56
+ *
57
+ * Entries come from `routing.children("/")`, minus plumbing (no `title`), minus any route with no page
58
+ * of its own to link to, and minus anything the user context gates out. Home, when declared, is derived off the trail and rendered as the sidebar's
59
+ * header entry and as the bottom bar's first item. It is current on the exact root and nowhere else —
60
+ * every route is below it — while every other entry is current for its whole subtree, so exactly one
61
+ * entry is ever marked, in either form.
62
+ *
63
+ * The bar form has no sidebar, and so no brand, no footer and no user block. All three move into the
64
+ * sheet behind the "More" item, which is therefore shown whenever there is anything to put there —
65
+ * overflowing entries, a `brand` snippet, a `footer` snippet or a signed-in user — and not only on an
66
+ * overflow. The sheet reads brand, then the entries that did not fit, then `footer`, then the user
67
+ * block with its sign-out control. Choosing an entry dismisses it; using a footer control does not.
68
+ *
69
+ * Icons: `meta.icon` is an uninterpreted string, so by default it is handed straight to markup as
70
+ * `<span class={meta.icon}>` — the shape an icon font or an Iconify/Tailwind class wants. Pass the
71
+ * `icon` snippet to render something else, an SVG component for instance, and `meta.icon` becomes
72
+ * whatever the app wants it to mean.
73
+ *
74
+ * The shell fills its parent, so give it one with a height (`<div class="h-dvh">`, or `html, body {
75
+ * height: 100% }`). It owns the page's scroll container — the bar is pinned to the bottom of the
76
+ * shell rather than to the window, and the content area reserves its height plus
77
+ * `env(safe-area-inset-bottom)` so nothing ends up underneath it.
78
+ *
79
+ * ```svelte
80
+ * <AppShell>
81
+ * {#snippet brand({ collapsed })}
82
+ * <a href="/">{collapsed ? "A" : "Acme"}</a>
83
+ * {/snippet}
84
+ * {#snippet footer()}
85
+ * <ThemeToggle />
86
+ * {/snippet}
87
+ * {@render page()}
88
+ * </AppShell>
89
+ * ```
90
+ */
91
+ declare const AppShell: import("svelte").Component<Props, {}, "collapsed">;
92
+ type AppShell = ReturnType<typeof AppShell>;
93
+ export default AppShell;
@@ -0,0 +1,78 @@
1
+ <script lang="ts">
2
+ /* Same reason as in `AppShell`: the lint rule can only see a literal `resolve()`, and a runtime
3
+ pathname has no literal route to give it. */
4
+ /* eslint-disable svelte/no-navigation-without-resolve */
5
+ import { getRoutingContext, resolvePathname } from "../routing.svelte.js";
6
+ import type { Crumb } from "./crumbs.js";
7
+
8
+ interface Props {
9
+ /**
10
+ * A trail to render verbatim, root first. Nothing is filtered — the caller already chose what
11
+ * to show — so keep `routeId` unique across entries. Omit it and the trail comes from the
12
+ * routing context, which then has to be set.
13
+ */
14
+ crumbs?: Crumb[];
15
+ /** Accessible name of the landmark. A prop because the library ships no translations. */
16
+ label?: string;
17
+ }
18
+
19
+ const { crumbs: custom, label = "Breadcrumb" }: Props = $props();
20
+
21
+ // The context getter THROWS when nothing set one, so it is only reached for when the caller did
22
+ // not bring their own trail. That is what lets custom breadcrumbs work in an app with no routing
23
+ // context at all. Whether an instance is custom or context-driven is settled at init, on purpose:
24
+ // swapping a live component between the two modes is not a thing worth supporting.
25
+ // svelte-ignore state_referenced_locally
26
+ const routing = custom === undefined ? getRoutingContext() : null;
27
+
28
+ // Plumbing entries stay in `matched` so a role gate declared halfway up the tree remains
29
+ // reachable; they have no business being a crumb.
30
+ const trail = $derived(custom ?? routing!.matched.filter((r) => r.meta.title !== undefined));
31
+ </script>
32
+
33
+ <!--
34
+ @component
35
+ The page's breadcrumb trail, derived from the routing context: every titled entry on the current
36
+ match, root first, each one a link except the last and except any section with no page behind it.
37
+
38
+ Pass `crumbs` to render a trail of your own instead — same grammar, caller-owned content. An entry
39
+ only needs `routeId` and `label`, so appending a leaf the route tree cannot know about is cheap:
40
+
41
+ ```svelte
42
+ <Breadcrumbs crumbs={[...routing.matched, { routeId: "leaf", label: order.reference }]} />
43
+ ```
44
+ -->
45
+
46
+ <nav class="breadcrumbs py-1 text-sm" aria-label={label}>
47
+ <ul>
48
+ {#each trail as crumb, index (crumb.routeId)}
49
+ {@const isLast = index === trail.length - 1}
50
+ {@const isLink = !isLast && crumb.navigable === true && crumb.pathname !== undefined}
51
+ <li>
52
+ {#if isLink}
53
+ <a class="crumb gap-1.5 text-base-content/50" href={resolvePathname(crumb.pathname!)}>
54
+ {#if crumb.routeId === "/" && crumb.meta?.icon}
55
+ <span class="size-4 {crumb.meta.icon}" aria-hidden="true"></span>
56
+ {/if}
57
+ {crumb.label}
58
+ </a>
59
+ {:else}
60
+ <!-- The last crumb names the page the user is already on, which is what `aria-current`
61
+ is for; the intermediate non-links are headings and get neither. -->
62
+ <span
63
+ class={[
64
+ "crumb cursor-default gap-1.5 no-underline",
65
+ isLast ? "text-base-content" : "text-base-content/50",
66
+ ]}
67
+ aria-current={isLast ? "page" : undefined}
68
+ >
69
+ {#if crumb.routeId === "/" && crumb.meta?.icon}
70
+ <span class="size-4 {crumb.meta.icon}" aria-hidden="true"></span>
71
+ {/if}
72
+ {crumb.label}
73
+ </span>
74
+ {/if}
75
+ </li>
76
+ {/each}
77
+ </ul>
78
+ </nav>
@@ -0,0 +1,25 @@
1
+ import type { Crumb } from "./crumbs.js";
2
+ interface Props {
3
+ /**
4
+ * A trail to render verbatim, root first. Nothing is filtered — the caller already chose what
5
+ * to show — so keep `routeId` unique across entries. Omit it and the trail comes from the
6
+ * routing context, which then has to be set.
7
+ */
8
+ crumbs?: Crumb[];
9
+ /** Accessible name of the landmark. A prop because the library ships no translations. */
10
+ label?: string;
11
+ }
12
+ /**
13
+ * The page's breadcrumb trail, derived from the routing context: every titled entry on the current
14
+ * match, root first, each one a link except the last and except any section with no page behind it.
15
+ *
16
+ * Pass `crumbs` to render a trail of your own instead — same grammar, caller-owned content. An entry
17
+ * only needs `routeId` and `label`, so appending a leaf the route tree cannot know about is cheap:
18
+ *
19
+ * ```svelte
20
+ * <Breadcrumbs crumbs={[...routing.matched, { routeId: "leaf", label: order.reference }]} />
21
+ * ```
22
+ */
23
+ declare const Breadcrumbs: import("svelte").Component<Props, {}, "">;
24
+ type Breadcrumbs = ReturnType<typeof Breadcrumbs>;
25
+ export default Breadcrumbs;
@@ -0,0 +1,23 @@
1
+ import type { RouteMeta } from "../routing.svelte.js";
2
+ /**
3
+ * One step of a breadcrumb trail.
4
+ *
5
+ * Deliberately a subset of {@link ResolvedRoute} rather than that type itself, so a page can
6
+ * append a leaf the route tree knows nothing about — a record's name it just fetched — with two
7
+ * fields instead of five.
8
+ *
9
+ * It lives in its own module rather than inside `Breadcrumbs.svelte` because a type declared in a
10
+ * component's instance script cannot be re-exported from the package's entry point, and a consumer
11
+ * building a custom trail needs to name this.
12
+ */
13
+ export interface Crumb {
14
+ /** Keys the entry; `"/"` additionally marks the root, the only crumb that shows an icon. */
15
+ routeId: string;
16
+ /** The visible text. */
17
+ label: string;
18
+ /** Where a navigable, non-final crumb links. */
19
+ pathname?: string;
20
+ /** Whether this crumb links at all. A section with no page of its own is a heading. */
21
+ navigable?: boolean;
22
+ meta?: Pick<RouteMeta, "icon">;
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export { default as AppShell } from "./AppShell.svelte";
2
+ export { default as Breadcrumbs } from "./Breadcrumbs.svelte";
3
+ export type { Crumb } from "./crumbs.js";
@@ -0,0 +1,6 @@
1
+ // The application chrome around a routed page. Everything here reads the routing context rather
2
+ // than a tree of its own, so the navigation an app declares once in `routes.ts` is the navigation
3
+ // that renders — on a sidebar, on a bottom bar and in a breadcrumb trail alike.
4
+ // `@viniaraujo68/plinth/shell`.
5
+ export { default as AppShell } from "./AppShell.svelte";
6
+ export { default as Breadcrumbs } from "./Breadcrumbs.svelte";
@@ -0,0 +1,249 @@
1
+ <script lang="ts" generics="T">
2
+ import Dropdown from "../components/Dropdown.svelte";
3
+ import {
4
+ canHideColumn,
5
+ isColumnHideable,
6
+ isColumnStateEmpty,
7
+ isColumnVisible,
8
+ moveColumn,
9
+ orderColumns,
10
+ setColumnVisible,
11
+ } from "./columns.js";
12
+ import type { Column, ColumnState } from "./types.js";
13
+
14
+ interface Props {
15
+ columns: readonly Column<T>[];
16
+ layout: ColumnState | undefined;
17
+ onchange: (layout: ColumnState | undefined) => void;
18
+ /** The trigger's text, and the menu's accessible name. */
19
+ label: string;
20
+ resetLabel: string;
21
+ moveLabel: (column: Column<T>, direction: "up" | "down") => string;
22
+ }
23
+
24
+ let { columns, layout, onchange, label, resetLabel, moveLabel }: Props = $props();
25
+
26
+ const ordered = $derived(orderColumns(columns, layout));
27
+
28
+ let dragging = $state<string | null>(null);
29
+ let target = $state<{ key: string; after: boolean } | null>(null);
30
+
31
+ const move = (key: string, to: number) => onchange(moveColumn(columns, layout, key, to));
32
+
33
+ const toggle = (column: Column<T>, visible: boolean) =>
34
+ onchange(setColumnVisible(layout, column.key, visible));
35
+
36
+ const onDragStart = (event: DragEvent, key: string) => {
37
+ dragging = key;
38
+ // Firefox starts no drag at all without data on the transfer, whatever the data is.
39
+ event.dataTransfer?.setData("text/plain", key);
40
+ if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
41
+ };
42
+
43
+ const onDragOver = (event: DragEvent, key: string) => {
44
+ if (dragging === null) return;
45
+ event.preventDefault();
46
+ const row = event.currentTarget as HTMLElement;
47
+ const box = row.getBoundingClientRect();
48
+ target = { key, after: event.clientY > box.top + box.height / 2 };
49
+ };
50
+
51
+ const onDrop = (event: DragEvent) => {
52
+ event.preventDefault();
53
+ if (dragging !== null && target !== null && target.key !== dragging) {
54
+ const keys = ordered.map((column) => column.key);
55
+ const from = keys.indexOf(dragging);
56
+ let to = keys.indexOf(target.key) + (target.after ? 1 : 0);
57
+ // The dragged row leaves the list before it is put back, so every slot past it moves up one.
58
+ if (from < to) to -= 1;
59
+ move(dragging, to);
60
+ }
61
+ onDragEnd();
62
+ };
63
+
64
+ const onDragEnd = () => {
65
+ dragging = null;
66
+ target = null;
67
+ };
68
+ </script>
69
+
70
+ <!--
71
+ @component
72
+ Internal to `DataTable`: the menu that shows, hides and reorders its columns. It is a `Dropdown`,
73
+ so Escape, a click outside and the trigger itself all close it without a listener of this file's
74
+ own.
75
+
76
+ Reordering is offered twice. Dragging a row is the quick way with a mouse, and it is native HTML
77
+ drag and drop rather than pointer bookkeeping, so the browser draws the ghost and scrolls the list.
78
+ The two arrow buttons on every row do the same one step at a time, and they are the only way to
79
+ reorder from a keyboard or a screen reader — drag and drop has no keyboard story of its own.
80
+
81
+ A column that is not hideable is listed without a checkbox, so the reader still sees it and can
82
+ still move it; the last visible column's checkbox is disabled rather than hidden, for the same
83
+ reason.
84
+ -->
85
+
86
+ <Dropdown
87
+ class="plinth-columns-trigger btn btn-ghost btn-sm"
88
+ panelClass="plinth-columns-panel rounded-box border border-base-content/10 bg-base-100 p-2 shadow-lg"
89
+ aria-label={label}
90
+ >
91
+ {label}
92
+ <span class="plinth-columns-caret" aria-hidden="true">▾</span>
93
+ {#snippet content()}
94
+ <ul class="plinth-columns-list" aria-label={label}>
95
+ {#each ordered as column, index (column.key)}
96
+ {@const visible = isColumnVisible(column, layout)}
97
+ <li
98
+ class="plinth-columns-row"
99
+ class:plinth-dragging={dragging === column.key}
100
+ class:plinth-drop-before={target?.key === column.key && !target.after}
101
+ class:plinth-drop-after={target?.key === column.key && target.after}
102
+ draggable="true"
103
+ ondragstart={(event) => onDragStart(event, column.key)}
104
+ ondragover={(event) => onDragOver(event, column.key)}
105
+ ondrop={onDrop}
106
+ ondragend={onDragEnd}
107
+ >
108
+ <span class="plinth-columns-grip" aria-hidden="true">⠿</span>
109
+ {#if isColumnHideable(column)}
110
+ <label class="plinth-columns-label">
111
+ <input
112
+ type="checkbox"
113
+ class="checkbox checkbox-xs"
114
+ checked={visible}
115
+ disabled={visible && !canHideColumn(columns, layout, column.key)}
116
+ onchange={(event) => toggle(column, event.currentTarget.checked)}
117
+ />
118
+ {column.label}
119
+ </label>
120
+ {:else}
121
+ <span class="plinth-columns-label plinth-columns-locked">{column.label}</span>
122
+ {/if}
123
+ <button
124
+ type="button"
125
+ class="plinth-columns-move"
126
+ aria-label={moveLabel(column, "up")}
127
+ disabled={index === 0}
128
+ onclick={() => move(column.key, index - 1)}
129
+ >
130
+ ↑
131
+ </button>
132
+ <button
133
+ type="button"
134
+ class="plinth-columns-move"
135
+ aria-label={moveLabel(column, "down")}
136
+ disabled={index === ordered.length - 1}
137
+ onclick={() => move(column.key, index + 1)}
138
+ >
139
+ ↓
140
+ </button>
141
+ </li>
142
+ {/each}
143
+ </ul>
144
+ <button
145
+ type="button"
146
+ class="plinth-columns-reset btn btn-ghost btn-xs"
147
+ disabled={isColumnStateEmpty(layout)}
148
+ onclick={() => onchange(undefined)}
149
+ >
150
+ {resetLabel}
151
+ </button>
152
+ {/snippet}
153
+ </Dropdown>
154
+
155
+ <style>
156
+ .plinth-columns-caret {
157
+ font-size: 0.7em;
158
+ opacity: 0.6;
159
+ }
160
+
161
+ :global(.plinth-columns-panel) {
162
+ display: flex;
163
+ flex-direction: column;
164
+ gap: 0.25rem;
165
+ min-width: 14rem;
166
+ max-height: 22rem;
167
+ margin: 0.25rem 0 0;
168
+ overflow-y: auto;
169
+ }
170
+
171
+ .plinth-columns-list {
172
+ display: flex;
173
+ flex-direction: column;
174
+ }
175
+
176
+ .plinth-columns-row {
177
+ display: grid;
178
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
179
+ align-items: center;
180
+ gap: 0.375rem;
181
+ padding: 0.25rem 0.375rem;
182
+ border-radius: var(--radius-field, 0.5rem);
183
+ /* Borders on both edges from the start, so showing the drop line does not shift the rows. */
184
+ border-top: 2px solid transparent;
185
+ border-bottom: 2px solid transparent;
186
+ }
187
+
188
+ .plinth-columns-row:hover {
189
+ background: color-mix(in oklch, var(--color-base-content) 5%, transparent);
190
+ }
191
+
192
+ .plinth-dragging {
193
+ opacity: 0.4;
194
+ }
195
+
196
+ .plinth-drop-before {
197
+ border-top-color: var(--color-primary);
198
+ }
199
+
200
+ .plinth-drop-after {
201
+ border-bottom-color: var(--color-primary);
202
+ }
203
+
204
+ .plinth-columns-grip {
205
+ cursor: grab;
206
+ color: color-mix(in oklch, var(--color-base-content) 40%, transparent);
207
+ user-select: none;
208
+ }
209
+
210
+ .plinth-columns-label {
211
+ display: flex;
212
+ align-items: center;
213
+ gap: 0.5rem;
214
+ min-width: 0;
215
+ font-size: 0.875rem;
216
+ cursor: pointer;
217
+ overflow-wrap: anywhere;
218
+ }
219
+
220
+ .plinth-columns-locked {
221
+ padding-left: calc(1rem + 0.5rem);
222
+ cursor: default;
223
+ color: color-mix(in oklch, var(--color-base-content) 65%, transparent);
224
+ }
225
+
226
+ .plinth-columns-move {
227
+ display: grid;
228
+ place-items: center;
229
+ width: 1.5rem;
230
+ height: 1.5rem;
231
+ border-radius: var(--radius-field, 0.5rem);
232
+ color: color-mix(in oklch, var(--color-base-content) 60%, transparent);
233
+ cursor: pointer;
234
+ }
235
+
236
+ .plinth-columns-move:hover:not(:disabled) {
237
+ background: color-mix(in oklch, var(--color-base-content) 10%, transparent);
238
+ color: var(--color-base-content);
239
+ }
240
+
241
+ .plinth-columns-move:disabled {
242
+ opacity: 0.3;
243
+ cursor: default;
244
+ }
245
+
246
+ .plinth-columns-reset {
247
+ align-self: flex-end;
248
+ }
249
+ </style>
@@ -0,0 +1,47 @@
1
+ import type { Column, ColumnState } from "./types.js";
2
+ declare function $$render<T>(): {
3
+ props: {
4
+ columns: readonly Column<T>[];
5
+ layout: ColumnState | undefined;
6
+ onchange: (layout: ColumnState | undefined) => void;
7
+ /** The trigger's text, and the menu's accessible name. */
8
+ label: string;
9
+ resetLabel: string;
10
+ moveLabel: (column: Column<T>, direction: "up" | "down") => string;
11
+ };
12
+ exports: {};
13
+ bindings: "";
14
+ slots: {};
15
+ events: {};
16
+ };
17
+ declare class __sveltets_Render<T> {
18
+ props(): ReturnType<typeof $$render<T>>['props'];
19
+ events(): ReturnType<typeof $$render<T>>['events'];
20
+ slots(): ReturnType<typeof $$render<T>>['slots'];
21
+ bindings(): "";
22
+ exports(): {};
23
+ }
24
+ interface $$IsomorphicComponent {
25
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
26
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
27
+ } & ReturnType<__sveltets_Render<T>['exports']>;
28
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
29
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
30
+ }
31
+ /**
32
+ * Internal to `DataTable`: the menu that shows, hides and reorders its columns. It is a `Dropdown`,
33
+ * so Escape, a click outside and the trigger itself all close it without a listener of this file's
34
+ * own.
35
+ *
36
+ * Reordering is offered twice. Dragging a row is the quick way with a mouse, and it is native HTML
37
+ * drag and drop rather than pointer bookkeeping, so the browser draws the ghost and scrolls the list.
38
+ * The two arrow buttons on every row do the same one step at a time, and they are the only way to
39
+ * reorder from a keyboard or a screen reader — drag and drop has no keyboard story of its own.
40
+ *
41
+ * A column that is not hideable is listed without a checkbox, so the reader still sees it and can
42
+ * still move it; the last visible column's checkbox is disabled rather than hidden, for the same
43
+ * reason.
44
+ */
45
+ declare const ColumnMenu: $$IsomorphicComponent;
46
+ type ColumnMenu<T> = InstanceType<typeof ColumnMenu<T>>;
47
+ export default ColumnMenu;