@poodle64/ui 2026.10.3 → 2026.10.5
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/README.md +105 -7
- package/dist/components/ui/context-column/context-column.svelte +118 -21
- package/dist/components/ui/context-column/context-column.svelte.d.ts +24 -1
- package/dist/components/ui/document-pane/document-pane.svelte +136 -0
- package/dist/components/ui/document-pane/document-pane.svelte.d.ts +43 -0
- package/dist/components/ui/document-pane/index.d.ts +2 -0
- package/dist/components/ui/document-pane/index.js +2 -0
- package/dist/components/ui/ledger/head-cell.svelte +73 -0
- package/dist/components/ui/ledger/head-cell.svelte.d.ts +19 -0
- package/dist/components/ui/ledger/index.d.ts +1 -1
- package/dist/components/ui/ledger/ledger-columns.svelte +29 -21
- package/dist/components/ui/ledger/ledger-columns.svelte.d.ts +50 -13
- package/dist/components/ui/ledger/ledger.d.ts +6 -0
- package/dist/components/ui/ledger/ledger.js +9 -1
- package/dist/components/ui/ledger/ledger.svelte +65 -84
- package/dist/components/ui/ledger/ledger.svelte.d.ts +6 -1
- package/dist/components/ui/ledger/look.d.ts +32 -0
- package/dist/components/ui/ledger/look.js +32 -0
- package/dist/components/ui/ledger/types.d.ts +21 -11
- package/dist/components/ui/list-toolbar/icon-action.svelte +42 -0
- package/dist/components/ui/list-toolbar/icon-action.svelte.d.ts +7 -0
- package/dist/components/ui/list-toolbar/index.d.ts +3 -0
- package/dist/components/ui/list-toolbar/index.js +2 -0
- package/dist/components/ui/list-toolbar/list-toolbar.svelte +85 -0
- package/dist/components/ui/list-toolbar/list-toolbar.svelte.d.ts +32 -0
- package/dist/components/ui/list-toolbar/types.d.ts +18 -0
- package/dist/components/ui/list-toolbar/types.js +1 -0
- package/dist/components/ui/panel/panel.svelte +8 -1
- package/dist/components/ui/panel/panel.svelte.d.ts +6 -0
- package/dist/components/ui/record-list/index.d.ts +3 -0
- package/dist/components/ui/record-list/index.js +2 -0
- package/dist/components/ui/record-list/record-list.svelte +489 -0
- package/dist/components/ui/record-list/record-list.svelte.d.ts +89 -0
- package/dist/components/ui/record-list/thumbnail.svelte +32 -0
- package/dist/components/ui/record-list/thumbnail.svelte.d.ts +14 -0
- package/dist/components/ui/record-list/types.d.ts +40 -0
- package/dist/components/ui/record-list/types.js +1 -0
- package/dist/components/ui/record-switcher/index.d.ts +3 -0
- package/dist/components/ui/record-switcher/index.js +2 -0
- package/dist/components/ui/record-switcher/record-switcher.svelte +167 -0
- package/dist/components/ui/record-switcher/record-switcher.svelte.d.ts +28 -0
- package/dist/components/ui/record-switcher/types.d.ts +22 -0
- package/dist/components/ui/record-switcher/types.js +1 -0
- package/package.json +1 -1
- package/registry/component-map.json +34 -6
- package/registry/component-map.md +8 -4
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
/** What the head says. */
|
|
3
|
+
label: string;
|
|
4
|
+
/** The column's whole name, for the sort button's label. */
|
|
5
|
+
name: string;
|
|
6
|
+
align?: 'start' | 'end';
|
|
7
|
+
sortable: boolean;
|
|
8
|
+
/** This column's sort, when it is the one in force. */
|
|
9
|
+
sort: 'asc' | 'desc' | null;
|
|
10
|
+
/** The direction the first click sorts in. */
|
|
11
|
+
first?: 'asc' | 'desc';
|
|
12
|
+
onsort: () => void;
|
|
13
|
+
/** The `data-slot` prefix: the list that owns the head. */
|
|
14
|
+
owner: string;
|
|
15
|
+
class?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const HeadCell: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
18
|
+
type HeadCell = ReturnType<typeof HeadCell>;
|
|
19
|
+
export default HeadCell;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default as Ledger } from './ledger.svelte';
|
|
2
2
|
export { default } from './ledger.svelte';
|
|
3
|
-
export type { LedgerColumn, LedgerOpenContext, LedgerOrigin, LedgerPeriod, LedgerPreferences, LedgerRow } from './types.js';
|
|
3
|
+
export type { LedgerColumn, LedgerOpenContext, LedgerOrigin, LedgerPeriod, LedgerPreferences, LedgerRow, ListColumn } from './types.js';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="P extends { columns: string[]; period?: LedgerPeriod }">
|
|
2
2
|
/**
|
|
3
3
|
* The viewer's Columns menu: every column the module offers as a tick (the
|
|
4
|
-
* running balance one more, always last), then the
|
|
5
|
-
* An icon button named by its tooltip.
|
|
4
|
+
* ledger's running balance one more, always last), then, where the list
|
|
5
|
+
* groups by a period, the period. An icon button named by its tooltip.
|
|
6
|
+
* The RecordList's menu is this one without the period.
|
|
6
7
|
*/
|
|
7
8
|
import Columns3 from '@lucide/svelte/icons/columns-3';
|
|
8
9
|
import { Button, buttonVariants } from '../button/index.js';
|
|
@@ -12,20 +13,23 @@
|
|
|
12
13
|
import * as Tooltip from '../tooltip/index.js';
|
|
13
14
|
import { cn } from '../../../utils.js';
|
|
14
15
|
import { LEDGER_PERIODS } from './ledger.js';
|
|
15
|
-
import type { LedgerPeriod
|
|
16
|
+
import type { LedgerPeriod } from './types.js';
|
|
16
17
|
|
|
17
18
|
let {
|
|
18
19
|
offered,
|
|
19
20
|
preferences,
|
|
20
21
|
note,
|
|
21
22
|
onchange,
|
|
22
|
-
onreset
|
|
23
|
+
onreset,
|
|
24
|
+
owner = 'ledger'
|
|
23
25
|
}: {
|
|
24
26
|
offered: { key: string; label: string; locked: boolean }[];
|
|
25
|
-
preferences:
|
|
27
|
+
preferences: P;
|
|
26
28
|
note?: string;
|
|
27
|
-
onchange: (next:
|
|
29
|
+
onchange: (next: P) => void;
|
|
28
30
|
onreset: () => void;
|
|
31
|
+
/** The `data-slot` prefix: the list that owns the menu. */
|
|
32
|
+
owner?: string;
|
|
29
33
|
} = $props();
|
|
30
34
|
|
|
31
35
|
let open = $state(false);
|
|
@@ -48,6 +52,8 @@
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
const eyebrow = 'text-muted-foreground text-2xs tracking-eyebrow font-semibold uppercase';
|
|
55
|
+
/** The ledger's menu also holds its running balance and its grouping. */
|
|
56
|
+
const name = $derived(preferences.period ? 'Columns, balance and grouping' : 'Columns');
|
|
51
57
|
</script>
|
|
52
58
|
|
|
53
59
|
<Popover.Root bind:open>
|
|
@@ -58,17 +64,17 @@
|
|
|
58
64
|
<Popover.Trigger
|
|
59
65
|
{...props}
|
|
60
66
|
class={buttonVariants({ variant: open ? 'outline' : 'ghost', size: 'icon-sm' })}
|
|
61
|
-
aria-label=
|
|
62
|
-
data-slot="
|
|
67
|
+
aria-label={name}
|
|
68
|
+
data-slot="{owner}-columns-trigger"
|
|
63
69
|
>
|
|
64
70
|
<Columns3 />
|
|
65
71
|
</Popover.Trigger>
|
|
66
72
|
{/snippet}
|
|
67
73
|
</Tooltip.Trigger>
|
|
68
|
-
<Tooltip.Content side="bottom">
|
|
74
|
+
<Tooltip.Content side="bottom">{name}</Tooltip.Content>
|
|
69
75
|
</Tooltip.Root>
|
|
70
76
|
</Tooltip.Provider>
|
|
71
|
-
<Popover.Content align="end" class="w-80 p-0" data-slot="
|
|
77
|
+
<Popover.Content align="end" class="w-80 p-0" data-slot="{owner}-columns">
|
|
72
78
|
<div class="flex flex-col px-2 pt-2.5 pb-2">
|
|
73
79
|
<span class={cn(eyebrow, 'px-1.5 pb-1.5')}>Columns</span>
|
|
74
80
|
{#each offered as c (c.key)}
|
|
@@ -89,16 +95,18 @@
|
|
|
89
95
|
</label>
|
|
90
96
|
{/each}
|
|
91
97
|
</div>
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
{#if preferences.period}
|
|
99
|
+
<div class="border-border flex flex-col gap-2 border-t px-3.5 pt-2.5 pb-3">
|
|
100
|
+
<span class={eyebrow}>Group by</span>
|
|
101
|
+
<Segmented
|
|
102
|
+
label="Group by"
|
|
103
|
+
options={LEDGER_PERIODS}
|
|
104
|
+
bind:value={() => preferences.period!, (period) => onchange({ ...preferences, period })}
|
|
105
|
+
class="grid w-full grid-cols-2"
|
|
106
|
+
/>
|
|
107
|
+
<span class="text-muted-foreground text-xs">{HINT[preferences.period]}</span>
|
|
108
|
+
</div>
|
|
109
|
+
{/if}
|
|
102
110
|
<div class="bg-surface-2 border-border flex items-center gap-2 border-t py-2 pr-2 pl-3.5">
|
|
103
111
|
<span class="text-muted-foreground min-w-0 flex-1 text-xs">{note ?? ''}</span>
|
|
104
112
|
<Button variant="ghost" size="sm" onclick={onreset}>Reset</Button>
|
|
@@ -1,15 +1,52 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import type { LedgerPeriod } from './types.js';
|
|
2
|
+
declare function $$render<P extends {
|
|
3
|
+
columns: string[];
|
|
4
|
+
period?: LedgerPeriod;
|
|
5
|
+
}>(): {
|
|
6
|
+
props: {
|
|
7
|
+
offered: {
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
locked: boolean;
|
|
11
|
+
}[];
|
|
12
|
+
preferences: P;
|
|
13
|
+
note?: string;
|
|
14
|
+
onchange: (next: P) => void;
|
|
15
|
+
onreset: () => void;
|
|
16
|
+
/** The `data-slot` prefix: the list that owns the menu. */
|
|
17
|
+
owner?: string;
|
|
18
|
+
};
|
|
19
|
+
exports: {};
|
|
20
|
+
bindings: "";
|
|
21
|
+
slots: {};
|
|
22
|
+
events: {};
|
|
12
23
|
};
|
|
13
|
-
declare
|
|
14
|
-
|
|
24
|
+
declare class __sveltets_Render<P extends {
|
|
25
|
+
columns: string[];
|
|
26
|
+
period?: LedgerPeriod;
|
|
27
|
+
}> {
|
|
28
|
+
props(): ReturnType<typeof $$render<P>>['props'];
|
|
29
|
+
events(): ReturnType<typeof $$render<P>>['events'];
|
|
30
|
+
slots(): ReturnType<typeof $$render<P>>['slots'];
|
|
31
|
+
bindings(): "";
|
|
32
|
+
exports(): {};
|
|
33
|
+
}
|
|
34
|
+
interface $$IsomorphicComponent {
|
|
35
|
+
new <P extends {
|
|
36
|
+
columns: string[];
|
|
37
|
+
period?: LedgerPeriod;
|
|
38
|
+
}>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<P>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<P>['props']>, ReturnType<__sveltets_Render<P>['events']>, ReturnType<__sveltets_Render<P>['slots']>> & {
|
|
39
|
+
$$bindings?: ReturnType<__sveltets_Render<P>['bindings']>;
|
|
40
|
+
} & ReturnType<__sveltets_Render<P>['exports']>;
|
|
41
|
+
<P extends {
|
|
42
|
+
columns: string[];
|
|
43
|
+
period?: LedgerPeriod;
|
|
44
|
+
}>(internal: unknown, props: ReturnType<__sveltets_Render<P>['props']> & {}): ReturnType<__sveltets_Render<P>['exports']>;
|
|
45
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
46
|
+
}
|
|
47
|
+
declare const LedgerColumns: $$IsomorphicComponent;
|
|
48
|
+
type LedgerColumns<P extends {
|
|
49
|
+
columns: string[];
|
|
50
|
+
period?: LedgerPeriod;
|
|
51
|
+
}> = InstanceType<typeof LedgerColumns<P>>;
|
|
15
52
|
export default LedgerColumns;
|
|
@@ -21,6 +21,12 @@ export declare function groupRows<R extends LedgerRow>(rows: readonly R[], perio
|
|
|
21
21
|
* rows without a value come last either way, and equal values keep newest first.
|
|
22
22
|
*/
|
|
23
23
|
export declare function sortRows<R extends LedgerRow>(rows: readonly R[], value: (row: R) => number | string | null | undefined, dir: 1 | -1): R[];
|
|
24
|
+
/**
|
|
25
|
+
* Any list's rows ordered by a value, `dir` 1 ascending and -1 descending:
|
|
26
|
+
* numbers as numbers, text with its numerals read as numbers. Rows without a
|
|
27
|
+
* value come last either way, and equal values keep the order they came in.
|
|
28
|
+
*/
|
|
29
|
+
export declare function orderBy<R>(rows: readonly R[], value: (row: R) => number | string | null | undefined, dir: 1 | -1): R[];
|
|
24
30
|
/** Whole cents, so a group's sum carries no float error. */
|
|
25
31
|
export declare function cents(value: number | string | null | undefined): number;
|
|
26
32
|
/** Money as the ledger prints it: in plain, out in brackets. */
|
|
@@ -127,7 +127,15 @@ export function groupRows(rows, period, fyStart = 7) {
|
|
|
127
127
|
* rows without a value come last either way, and equal values keep newest first.
|
|
128
128
|
*/
|
|
129
129
|
export function sortRows(rows, value, dir) {
|
|
130
|
-
|
|
130
|
+
return orderBy(groupRows(rows, 'none')[0]?.rows ?? [], value, dir);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Any list's rows ordered by a value, `dir` 1 ascending and -1 descending:
|
|
134
|
+
* numbers as numbers, text with its numerals read as numbers. Rows without a
|
|
135
|
+
* value come last either way, and equal values keep the order they came in.
|
|
136
|
+
*/
|
|
137
|
+
export function orderBy(rows, value, dir) {
|
|
138
|
+
const keyed = rows.map((row) => ({ row, v: value(row) }));
|
|
131
139
|
const missing = (v) => v === null || v === undefined || v === '';
|
|
132
140
|
keyed.sort((a, b) => {
|
|
133
141
|
if (missing(a.v) || missing(b.v))
|
|
@@ -18,14 +18,32 @@
|
|
|
18
18
|
import { DEV } from 'esm-env';
|
|
19
19
|
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
|
20
20
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
|
21
|
-
import ChevronUp from '@lucide/svelte/icons/chevron-up';
|
|
22
21
|
import Paperclip from '@lucide/svelte/icons/paperclip';
|
|
23
22
|
import X from '@lucide/svelte/icons/x';
|
|
24
23
|
import Button from '../button/button.svelte';
|
|
25
24
|
import { Checkbox } from '../checkbox/index.js';
|
|
26
25
|
import EmptyState from '../empty-state/empty-state.svelte';
|
|
27
26
|
import StatusBadge from '../status-badge/status-badge.svelte';
|
|
27
|
+
import ListToolbar from '../list-toolbar/list-toolbar.svelte';
|
|
28
|
+
import type { ListAction, ListIconAction } from '../list-toolbar/types.js';
|
|
29
|
+
import HeadCell from './head-cell.svelte';
|
|
28
30
|
import LedgerColumns from './ledger-columns.svelte';
|
|
31
|
+
import {
|
|
32
|
+
CARD,
|
|
33
|
+
CELL,
|
|
34
|
+
END,
|
|
35
|
+
GROUP,
|
|
36
|
+
GROUP_LABEL,
|
|
37
|
+
HEAD,
|
|
38
|
+
HEAD_GROUND,
|
|
39
|
+
RING,
|
|
40
|
+
RING_INSET,
|
|
41
|
+
ROW_HEIGHT,
|
|
42
|
+
ROW_TEXT,
|
|
43
|
+
RULE,
|
|
44
|
+
THROUGH,
|
|
45
|
+
groupSpacing
|
|
46
|
+
} from './look.js';
|
|
29
47
|
import { AU_LOCALE } from '../../../format.js';
|
|
30
48
|
import { cn } from '../../../utils.js';
|
|
31
49
|
import {
|
|
@@ -58,6 +76,8 @@
|
|
|
58
76
|
title,
|
|
59
77
|
meta,
|
|
60
78
|
actions,
|
|
79
|
+
tools = [],
|
|
80
|
+
action,
|
|
61
81
|
selected = $bindable([]),
|
|
62
82
|
bulkActions,
|
|
63
83
|
open = $bindable(null),
|
|
@@ -89,8 +109,12 @@
|
|
|
89
109
|
title?: string;
|
|
90
110
|
/** Beside the title. Defaults to the row count. */
|
|
91
111
|
meta?: string;
|
|
92
|
-
/** The toolbar's own
|
|
112
|
+
/** The toolbar's own controls, after the Columns menu and before `tools`. */
|
|
93
113
|
actions?: Snippet;
|
|
114
|
+
/** The toolbar's icon actions, each named by its tooltip: export, print. */
|
|
115
|
+
tools?: ListIconAction[];
|
|
116
|
+
/** The toolbar's one labelled action, last: add. */
|
|
117
|
+
action?: ListAction;
|
|
94
118
|
/** The ticked rows' ids. Ticking needs `bulkActions`. */
|
|
95
119
|
selected?: Id[];
|
|
96
120
|
/** What the ticked rows allow, in the bar that replaces the toolbar. */
|
|
@@ -135,21 +159,6 @@
|
|
|
135
159
|
/** Ticking adds a checkbox to the gutter, so it widens to hold the chevron beside it. */
|
|
136
160
|
const GUTTER = '2.5rem';
|
|
137
161
|
const GUTTER_TICKS = '3.5rem';
|
|
138
|
-
const CELL = 'min-w-0 truncate px-2';
|
|
139
|
-
const END = 'pr-5 text-right';
|
|
140
|
-
/** The row's height rides the package's density ramp: 3.5rem, 3rem compact. */
|
|
141
|
-
const ROW_HEIGHT = 'min-height: calc(var(--ds-control-height-md) + 1rem);';
|
|
142
|
-
/** The house focus ring, and the same ring drawn inside a control that fills a clipped card. */
|
|
143
|
-
const RING = 'focus-visible:ring-ring/50 focus-visible:ring-3 focus-visible:outline-none';
|
|
144
|
-
const RING_INSET =
|
|
145
|
-
'focus-visible:inset-ring-ring/50 focus-visible:inset-ring-3 focus-visible:outline-none';
|
|
146
|
-
/**
|
|
147
|
-
* A cell over an openable row lets a click through to the row beneath it, and
|
|
148
|
-
* gives one back to any control a module's cell renders, so a link or a button
|
|
149
|
-
* in a cell is never silently inert.
|
|
150
|
-
*/
|
|
151
|
-
const THROUGH =
|
|
152
|
-
'pointer-events-none [&_:is(a,button,input,select,textarea,label,summary,[role=button],[role=link],[role=checkbox],[tabindex])]:pointer-events-auto';
|
|
153
162
|
|
|
154
163
|
// ── Columns: the ledger's own three, the module's, the balance last ──
|
|
155
164
|
const resolved = $derived.by(() => {
|
|
@@ -177,7 +186,9 @@
|
|
|
177
186
|
});
|
|
178
187
|
|
|
179
188
|
const defaults = $derived<LedgerPreferences>({
|
|
180
|
-
columns: resolved
|
|
189
|
+
columns: resolved
|
|
190
|
+
.filter((c) => !c.locked && (c.on === true || (c.on === 'wide' && WIDE)))
|
|
191
|
+
.map((c) => c.key),
|
|
181
192
|
period: 'month'
|
|
182
193
|
});
|
|
183
194
|
/** What the consumer kept, read field by field: a stale or foreign key falls back. */
|
|
@@ -220,9 +231,9 @@
|
|
|
220
231
|
/** What a column sorts by; the running balance depends on the order, so never it. */
|
|
221
232
|
const sorter = (c: { key: string; sort?: LedgerColumn<R>['sort']; text?: LedgerColumn<R>['text'] }) =>
|
|
222
233
|
c.key === 'balance' ? undefined : (c.sort ?? c.text);
|
|
223
|
-
/** Click a head to sort by it, largest or newest first; again to reverse. */
|
|
234
|
+
/** Click a head to sort by it, largest or newest first; again to reverse; a third time back to the default order. */
|
|
224
235
|
function sortBy(key: string) {
|
|
225
|
-
sort = sort?.key
|
|
236
|
+
sort = sort?.key !== key ? { key, dir: -1 } : sort.dir === -1 ? { key, dir: 1 } : null;
|
|
226
237
|
}
|
|
227
238
|
/** The sort in force: a column the viewer has since unticked no longer sorts. */
|
|
228
239
|
const activeSort = $derived.by(() => {
|
|
@@ -231,8 +242,8 @@
|
|
|
231
242
|
? sort
|
|
232
243
|
: null;
|
|
233
244
|
});
|
|
234
|
-
/**
|
|
235
|
-
const grouped = $derived(prefs.period !== 'none'
|
|
245
|
+
/** A sort orders rows inside their period's groups; only `none` is the flat list. */
|
|
246
|
+
const grouped = $derived(prefs.period !== 'none');
|
|
236
247
|
const BY_DAY = $derived(grouped && prefs.period === 'day');
|
|
237
248
|
|
|
238
249
|
const shown = $derived(
|
|
@@ -262,8 +273,9 @@
|
|
|
262
273
|
});
|
|
263
274
|
const groups = $derived.by(() => {
|
|
264
275
|
const by = activeSort && sorter(resolved.find((c) => c.key === activeSort.key)!);
|
|
265
|
-
|
|
266
|
-
|
|
276
|
+
const base = groupRows(rows, prefs.period, fy);
|
|
277
|
+
if (!activeSort || !by) return base;
|
|
278
|
+
return base.map((g) => ({ key: g.key, rows: sortRows(g.rows, by, activeSort.dir) }));
|
|
267
279
|
});
|
|
268
280
|
/** Closed groups, keyed with their period so a new period starts open. */
|
|
269
281
|
const closed = new SvelteSet<string>();
|
|
@@ -637,7 +649,7 @@
|
|
|
637
649
|
<div
|
|
638
650
|
class={cn(
|
|
639
651
|
'relative grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 px-3 py-2 text-sm',
|
|
640
|
-
!first &&
|
|
652
|
+
!first && RULE,
|
|
641
653
|
openable && 'hover:bg-accent/40'
|
|
642
654
|
)}
|
|
643
655
|
style={ROW_HEIGHT}
|
|
@@ -676,8 +688,9 @@
|
|
|
676
688
|
{:else}
|
|
677
689
|
<div
|
|
678
690
|
class={cn(
|
|
679
|
-
'relative grid items-stretch
|
|
680
|
-
|
|
691
|
+
'relative grid items-stretch',
|
|
692
|
+
ROW_TEXT,
|
|
693
|
+
!first && RULE,
|
|
681
694
|
on && 'bg-primary/9',
|
|
682
695
|
openable && !on && 'hover:bg-accent/40'
|
|
683
696
|
)}
|
|
@@ -725,12 +738,13 @@
|
|
|
725
738
|
{@const net = single ? '' : money(t.net)}
|
|
726
739
|
{@const netTone = t.net > 0 ? 'text-status-success' : 'text-muted-foreground'}
|
|
727
740
|
{@const klass = cn(
|
|
728
|
-
|
|
741
|
+
GROUP,
|
|
742
|
+
'w-full text-left',
|
|
729
743
|
PHONE ? 'flex items-center gap-2 px-0.5' : 'grid items-center px-px'
|
|
730
744
|
)}
|
|
731
745
|
<!-- The spacing is padding, not margin, so the measured height is the height it takes. -->
|
|
732
746
|
<div
|
|
733
|
-
class={
|
|
747
|
+
class={groupSpacing(gi)}
|
|
734
748
|
{@attach measure(`h:${key}`, gi > 0 ? 'head' : undefined)}
|
|
735
749
|
>
|
|
736
750
|
<!-- A day heads its rows and does not close; any other period's label opens and closes it. -->
|
|
@@ -769,7 +783,7 @@
|
|
|
769
783
|
{#if !BY_DAY}<ChevronRight
|
|
770
784
|
class={cn('size-3.5 flex-none transition-transform', isOpen && 'rotate-90')}
|
|
771
785
|
/>{/if}
|
|
772
|
-
<span class=
|
|
786
|
+
<span class={cn(GROUP_LABEL, 'whitespace-nowrap')}>{label}</span>
|
|
773
787
|
<span class="min-w-0 flex-1 truncate">{metaText}</span>
|
|
774
788
|
<span class={cn('tabular-nums', netTone)}>{net}</span>
|
|
775
789
|
{:else}
|
|
@@ -785,7 +799,7 @@
|
|
|
785
799
|
class="flex min-w-0 items-baseline gap-3 overflow-hidden px-2 whitespace-nowrap"
|
|
786
800
|
style="grid-column: 2 / {cAmount};"
|
|
787
801
|
>
|
|
788
|
-
<span class=
|
|
802
|
+
<span class={GROUP_LABEL}>{label}</span>
|
|
789
803
|
<span class="min-w-0 truncate">{metaText}</span>
|
|
790
804
|
</span>
|
|
791
805
|
<span class={cn(CELL, END, 'tabular-nums', netTone)} style="grid-column: {cAmount};">{net}</span
|
|
@@ -834,18 +848,15 @@
|
|
|
834
848
|
</Button>
|
|
835
849
|
</div>
|
|
836
850
|
{:else}
|
|
837
|
-
<
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
851
|
+
<ListToolbar
|
|
852
|
+
{title}
|
|
853
|
+
meta={meta ?? count(rows.length)}
|
|
854
|
+
{tools}
|
|
855
|
+
{action}
|
|
856
|
+
label="This ledger"
|
|
857
|
+
style="padding-right: {edge};"
|
|
842
858
|
data-slot="ledger-toolbar"
|
|
843
859
|
>
|
|
844
|
-
<span class="flex min-w-0 items-baseline gap-2.5 overflow-hidden whitespace-nowrap">
|
|
845
|
-
{#if title}<span class="text-body flex-none font-semibold">{title}</span>{/if}
|
|
846
|
-
<span class="text-muted-foreground truncate text-sm">{meta ?? count(rows.length)}</span>
|
|
847
|
-
</span>
|
|
848
|
-
<span class="min-w-0 flex-1"></span>
|
|
849
860
|
<LedgerColumns
|
|
850
861
|
offered={resolved.map((c) => ({ key: c.key, label: c.label, locked: c.locked }))}
|
|
851
862
|
preferences={prefs}
|
|
@@ -854,7 +865,7 @@
|
|
|
854
865
|
onreset={() => setPreferences(defaults)}
|
|
855
866
|
/>
|
|
856
867
|
{@render actions?.()}
|
|
857
|
-
</
|
|
868
|
+
</ListToolbar>
|
|
858
869
|
{/if}
|
|
859
870
|
|
|
860
871
|
<div
|
|
@@ -867,9 +878,9 @@
|
|
|
867
878
|
data-slot="ledger-rows"
|
|
868
879
|
>
|
|
869
880
|
{#if !PHONE}
|
|
870
|
-
<div class=
|
|
881
|
+
<div class={HEAD_GROUND} bind:offsetHeight={headHeight}>
|
|
871
882
|
<div
|
|
872
|
-
class=
|
|
883
|
+
class={HEAD}
|
|
873
884
|
style:grid-template-columns={template}
|
|
874
885
|
data-slot="ledger-head"
|
|
875
886
|
>
|
|
@@ -904,46 +915,16 @@
|
|
|
904
915
|
{/if}
|
|
905
916
|
</span>
|
|
906
917
|
{#each shown as c (c.key)}
|
|
907
|
-
{@const sortable = !!sorter(c)}
|
|
908
918
|
{@const mine = activeSort?.key === c.key ? activeSort : null}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
c
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
class={cn(
|
|
919
|
-
headClass,
|
|
920
|
-
'group flex cursor-pointer items-center gap-0.5 rounded-md hover:text-foreground',
|
|
921
|
-
RING_INSET,
|
|
922
|
-
c.align === 'end' && 'justify-end',
|
|
923
|
-
mine && 'text-foreground'
|
|
924
|
-
)}
|
|
925
|
-
aria-label={mine
|
|
926
|
-
? `Sorted by ${c.label}, ${mine.dir === -1 ? 'descending' : 'ascending'}. Click to reverse`
|
|
927
|
-
: `Sort by ${c.label}`}
|
|
928
|
-
onclick={() => sortBy(c.key)}
|
|
929
|
-
data-slot="ledger-sort"
|
|
930
|
-
data-sort={mine ? (mine.dir === -1 ? 'desc' : 'asc') : undefined}
|
|
931
|
-
>
|
|
932
|
-
<span class="truncate">{label}</span>
|
|
933
|
-
{#if mine?.dir === 1}
|
|
934
|
-
<ChevronUp class="size-3 flex-none" />
|
|
935
|
-
{:else}
|
|
936
|
-
<ChevronDown
|
|
937
|
-
class={cn(
|
|
938
|
-
'size-3 flex-none',
|
|
939
|
-
!mine && 'opacity-0 group-hover:opacity-50 group-focus-visible:opacity-50'
|
|
940
|
-
)}
|
|
941
|
-
/>
|
|
942
|
-
{/if}
|
|
943
|
-
</button>
|
|
944
|
-
{:else}
|
|
945
|
-
<span class={headClass}>{label}</span>
|
|
946
|
-
{/if}
|
|
919
|
+
<HeadCell
|
|
920
|
+
label={c.head ?? c.label}
|
|
921
|
+
name={c.label}
|
|
922
|
+
align={c.align}
|
|
923
|
+
sortable={!!sorter(c)}
|
|
924
|
+
sort={mine ? (mine.dir === -1 ? 'desc' : 'asc') : null}
|
|
925
|
+
onsort={() => sortBy(c.key)}
|
|
926
|
+
owner="ledger"
|
|
927
|
+
/>
|
|
947
928
|
{/each}
|
|
948
929
|
</div>
|
|
949
930
|
</div>
|
|
@@ -973,7 +954,7 @@
|
|
|
973
954
|
<!-- A card the window cuts keeps no edge at the cut, which is out of view. -->
|
|
974
955
|
<div
|
|
975
956
|
class={cn(
|
|
976
|
-
|
|
957
|
+
CARD,
|
|
977
958
|
block.cutTop && 'rounded-t-none border-t-0',
|
|
978
959
|
block.cutBottom && 'rounded-b-none border-b-0'
|
|
979
960
|
)}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { Snippet } from 'svelte';
|
|
14
14
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
15
|
+
import type { ListAction, ListIconAction } from '../list-toolbar/types.js';
|
|
15
16
|
import type { LedgerColumn, LedgerOpenContext, LedgerPreferences, LedgerRow } from './types.js';
|
|
16
17
|
declare function $$render<R extends LedgerRow>(): {
|
|
17
18
|
props: Omit<HTMLAttributes<HTMLDivElement>, "title"> & {
|
|
@@ -33,8 +34,12 @@ declare function $$render<R extends LedgerRow>(): {
|
|
|
33
34
|
title?: string;
|
|
34
35
|
/** Beside the title. Defaults to the row count. */
|
|
35
36
|
meta?: string;
|
|
36
|
-
/** The toolbar's own
|
|
37
|
+
/** The toolbar's own controls, after the Columns menu and before `tools`. */
|
|
37
38
|
actions?: Snippet;
|
|
39
|
+
/** The toolbar's icon actions, each named by its tooltip: export, print. */
|
|
40
|
+
tools?: ListIconAction[];
|
|
41
|
+
/** The toolbar's one labelled action, last: add. */
|
|
42
|
+
action?: ListAction;
|
|
38
43
|
/** The ticked rows' ids. Ticking needs `bulkActions`. */
|
|
39
44
|
selected?: R["id"][];
|
|
40
45
|
/** What the ticked rows allow, in the bar that replaces the toolbar. */
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ledger's look, held once for every list that wears it: the Ledger and
|
|
3
|
+
* the RecordList. A head card, row cards with hairlines between rows, and each
|
|
4
|
+
* group's label on the page ground.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CELL = "min-w-0 truncate px-2";
|
|
7
|
+
export declare const END = "pr-5 text-right";
|
|
8
|
+
/** The row's height rides the package's density ramp: 3.5rem, 3rem compact. */
|
|
9
|
+
export declare const ROW_HEIGHT = "min-height: calc(var(--ds-control-height-md) + 1rem);";
|
|
10
|
+
/** The row text's size, between the body and the small print. */
|
|
11
|
+
export declare const ROW_TEXT = "text-[0.8125rem]";
|
|
12
|
+
/** The hairline between two rows of one card. */
|
|
13
|
+
export declare const RULE = "border-border/60 border-t";
|
|
14
|
+
/** The card that holds a group's rows. */
|
|
15
|
+
export declare const CARD = "bg-card border-border overflow-hidden rounded-lg border";
|
|
16
|
+
/** The head card, and the ground behind it that hides the rows scrolling under it. */
|
|
17
|
+
export declare const HEAD_GROUND = "bg-background sticky top-0 z-3 pb-3";
|
|
18
|
+
export declare const HEAD = "bg-card border-border grid h-9 items-center rounded-lg border";
|
|
19
|
+
export declare const HEAD_TEXT = "text-muted-foreground text-2xs tracking-eyebrow font-semibold uppercase";
|
|
20
|
+
/** A group's label line on the ground; the spacing is padding, so a measured height is the height it takes. */
|
|
21
|
+
export declare const GROUP = "text-muted-foreground h-6.5 text-xs";
|
|
22
|
+
export declare const GROUP_LABEL = "text-[0.8125rem] font-semibold";
|
|
23
|
+
export declare const groupSpacing: (index: number) => "pt-4.5 pb-1.5" | "pb-1.5";
|
|
24
|
+
/** The house focus ring, and the same ring drawn inside a control that fills a clipped card. */
|
|
25
|
+
export declare const RING = "focus-visible:ring-ring/50 focus-visible:ring-3 focus-visible:outline-none";
|
|
26
|
+
export declare const RING_INSET = "focus-visible:inset-ring-ring/50 focus-visible:inset-ring-3 focus-visible:outline-none";
|
|
27
|
+
/**
|
|
28
|
+
* A cell over an openable row lets a click through to the row beneath it, and
|
|
29
|
+
* gives one back to any control a module's cell renders, so a link or a button
|
|
30
|
+
* in a cell is never silently inert.
|
|
31
|
+
*/
|
|
32
|
+
export declare const THROUGH = "pointer-events-none [&_:is(a,button,input,select,textarea,label,summary,[role=button],[role=link],[role=checkbox],[tabindex])]:pointer-events-auto";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ledger's look, held once for every list that wears it: the Ledger and
|
|
3
|
+
* the RecordList. A head card, row cards with hairlines between rows, and each
|
|
4
|
+
* group's label on the page ground.
|
|
5
|
+
*/
|
|
6
|
+
export const CELL = 'min-w-0 truncate px-2';
|
|
7
|
+
export const END = 'pr-5 text-right';
|
|
8
|
+
/** The row's height rides the package's density ramp: 3.5rem, 3rem compact. */
|
|
9
|
+
export const ROW_HEIGHT = 'min-height: calc(var(--ds-control-height-md) + 1rem);';
|
|
10
|
+
/** The row text's size, between the body and the small print. */
|
|
11
|
+
export const ROW_TEXT = 'text-[0.8125rem]';
|
|
12
|
+
/** The hairline between two rows of one card. */
|
|
13
|
+
export const RULE = 'border-border/60 border-t';
|
|
14
|
+
/** The card that holds a group's rows. */
|
|
15
|
+
export const CARD = 'bg-card border-border overflow-hidden rounded-lg border';
|
|
16
|
+
/** The head card, and the ground behind it that hides the rows scrolling under it. */
|
|
17
|
+
export const HEAD_GROUND = 'bg-background sticky top-0 z-3 pb-3';
|
|
18
|
+
export const HEAD = 'bg-card border-border grid h-9 items-center rounded-lg border';
|
|
19
|
+
export const HEAD_TEXT = 'text-muted-foreground text-2xs tracking-eyebrow font-semibold uppercase';
|
|
20
|
+
/** A group's label line on the ground; the spacing is padding, so a measured height is the height it takes. */
|
|
21
|
+
export const GROUP = 'text-muted-foreground h-6.5 text-xs';
|
|
22
|
+
export const GROUP_LABEL = 'text-[0.8125rem] font-semibold';
|
|
23
|
+
export const groupSpacing = (index) => (index > 0 ? 'pt-4.5 pb-1.5' : 'pb-1.5');
|
|
24
|
+
/** The house focus ring, and the same ring drawn inside a control that fills a clipped card. */
|
|
25
|
+
export const RING = 'focus-visible:ring-ring/50 focus-visible:ring-3 focus-visible:outline-none';
|
|
26
|
+
export const RING_INSET = 'focus-visible:inset-ring-ring/50 focus-visible:inset-ring-3 focus-visible:outline-none';
|
|
27
|
+
/**
|
|
28
|
+
* A cell over an openable row lets a click through to the row beneath it, and
|
|
29
|
+
* gives one back to any control a module's cell renders, so a link or a button
|
|
30
|
+
* in a cell is never silently inert.
|
|
31
|
+
*/
|
|
32
|
+
export const THROUGH = 'pointer-events-none [&_:is(a,button,input,select,textarea,label,summary,[role=button],[role=link],[role=checkbox],[tabindex])]:pointer-events-auto';
|