@happyvertical/smrt-svelte 0.55.22 → 0.55.24
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/AGENTS.md +2 -2
- package/README.md +2 -2
- package/agents/overview-surfaces.md +108 -4
- package/agents/screens.md +171 -5
- package/dist/components/overview/index.d.ts +4 -1
- package/dist/components/overview/index.d.ts.map +1 -1
- package/dist/components/overview/index.js +3 -0
- package/dist/components/overview/record.d.ts +51 -0
- package/dist/components/overview/record.d.ts.map +1 -0
- package/dist/components/overview/record.js +146 -0
- package/dist/components/overview/server.d.ts +4 -0
- package/dist/components/overview/server.d.ts.map +1 -1
- package/dist/components/overview/server.js +4 -0
- package/dist/components/overview/types.d.ts +12 -0
- package/dist/components/overview/types.d.ts.map +1 -1
- package/dist/components/overview/widgets/LineItemsWidget.svelte +144 -0
- package/dist/components/overview/widgets/LineItemsWidget.svelte.d.ts +6 -0
- package/dist/components/overview/widgets/LineItemsWidget.svelte.d.ts.map +1 -0
- package/dist/components/overview/widgets/RecordListWidget.svelte +67 -3
- package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts.map +1 -1
- package/dist/components/overview/widgets/RecordTable.svelte +167 -0
- package/dist/components/overview/widgets/RecordTable.svelte.d.ts +21 -0
- package/dist/components/overview/widgets/RecordTable.svelte.d.ts.map +1 -0
- package/dist/components/overview/widgets/RelatedWidget.svelte +117 -0
- package/dist/components/overview/widgets/RelatedWidget.svelte.d.ts +6 -0
- package/dist/components/overview/widgets/RelatedWidget.svelte.d.ts.map +1 -0
- package/dist/components/overview/widgets/core.d.ts +13 -2
- package/dist/components/overview/widgets/core.d.ts.map +1 -1
- package/dist/components/overview/widgets/core.js +43 -2
- package/dist/components/overview/widgets/data.d.ts +97 -8
- package/dist/components/overview/widgets/data.d.ts.map +1 -1
- package/dist/components/overview/widgets/line-totals.d.ts +48 -0
- package/dist/components/overview/widgets/line-totals.d.ts.map +1 -0
- package/dist/components/overview/widgets/line-totals.js +91 -0
- package/dist/components/overview/widgets/record-loaders.d.ts +63 -0
- package/dist/components/overview/widgets/record-loaders.d.ts.map +1 -0
- package/dist/components/overview/widgets/record-loaders.js +150 -0
- package/dist/components/overview/widgets/record-table.d.ts +45 -0
- package/dist/components/overview/widgets/record-table.d.ts.map +1 -0
- package/dist/components/overview/widgets/record-table.js +201 -0
- package/dist/components/overview/widgets/record-widgets.d.ts +27 -0
- package/dist/components/overview/widgets/record-widgets.d.ts.map +1 -0
- package/dist/components/overview/widgets/record-widgets.js +100 -0
- package/dist/components/screens/DetailScreen.svelte +105 -6
- package/dist/components/screens/DetailScreen.svelte.d.ts.map +1 -1
- package/dist/components/screens/EditForm.svelte +39 -3
- package/dist/components/screens/EditForm.svelte.d.ts.map +1 -1
- package/dist/components/screens/RecipeScreens.svelte +18 -18
- package/dist/components/screens/RecipeScreens.svelte.d.ts.map +1 -1
- package/dist/components/screens/RecordHeader.svelte +238 -0
- package/dist/components/screens/RecordHeader.svelte.d.ts +26 -0
- package/dist/components/screens/RecordHeader.svelte.d.ts.map +1 -0
- package/dist/components/screens/RecordScreen.svelte +511 -0
- package/dist/components/screens/RecordScreen.svelte.d.ts +5 -0
- package/dist/components/screens/RecordScreen.svelte.d.ts.map +1 -0
- package/dist/components/screens/columns.d.ts +29 -0
- package/dist/components/screens/columns.d.ts.map +1 -0
- package/dist/components/screens/columns.js +115 -0
- package/dist/components/screens/fields.d.ts +12 -2
- package/dist/components/screens/fields.d.ts.map +1 -1
- package/dist/components/screens/fields.js +37 -5
- package/dist/components/screens/index.d.ts +7 -2
- package/dist/components/screens/index.d.ts.map +1 -1
- package/dist/components/screens/index.js +6 -1
- package/dist/components/screens/record-header.d.ts +51 -0
- package/dist/components/screens/record-header.d.ts.map +1 -0
- package/dist/components/screens/record-header.js +118 -0
- package/dist/components/screens/relations.d.ts +49 -0
- package/dist/components/screens/relations.d.ts.map +1 -0
- package/dist/components/screens/relations.js +98 -0
- package/dist/components/screens/roles.d.ts +56 -0
- package/dist/components/screens/roles.d.ts.map +1 -0
- package/dist/components/screens/roles.js +174 -0
- package/dist/components/screens/types.d.ts +182 -1
- package/dist/components/screens/types.d.ts.map +1 -1
- package/dist/components/screens/values.d.ts +23 -1
- package/dist/components/screens/values.d.ts.map +1 -1
- package/dist/components/screens/values.js +80 -4
- package/dist/i18n/strings.overview.d.ts +25 -0
- package/dist/i18n/strings.overview.d.ts.map +1 -1
- package/dist/i18n/strings.overview.js +25 -0
- package/dist/i18n/strings.screens.d.ts +8 -0
- package/dist/i18n/strings.screens.d.ts.map +1 -1
- package/dist/i18n/strings.screens.js +8 -0
- package/package.json +16 -16
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* RecordHeader (internal to `RecordScreen`, #3718 record page): a small
|
|
4
|
+
* back link above the title, the heading (a document leads with its
|
|
5
|
+
* identifier, "Order GRA-3159"), the monospace identifier, the status badge
|
|
6
|
+
* (toned by value, neutral when unmapped) and the key facts (party link,
|
|
7
|
+
* date, total money, contact). Actions: Edit is the one primary button; an
|
|
8
|
+
* overflow menu holds the host's `extraActions` and Delete, which asks for
|
|
9
|
+
* confirmation. The model is `deriveRecordHeader`.
|
|
10
|
+
*/
|
|
11
|
+
import { Alert, ConfirmDialog } from '@happyvertical/smrt-ui/feedback';
|
|
12
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
13
|
+
import { Badge, Button, Popover } from '@happyvertical/smrt-ui/ui';
|
|
14
|
+
import type { Snippet } from 'svelte';
|
|
15
|
+
import { M } from '../../i18n/strings.screens.js';
|
|
16
|
+
import type { RecordStatusTone } from '../overview/widgets/data.js';
|
|
17
|
+
import { statusLabel, statusTone } from '../overview/widgets/record-table.js';
|
|
18
|
+
import { screenTitles } from './fields.js';
|
|
19
|
+
import type { RecordFact, RecordHeaderModel } from './record-header.js';
|
|
20
|
+
import type { ScreenRelation } from './relations.js';
|
|
21
|
+
import type {
|
|
22
|
+
ScreenCollectionDefinition,
|
|
23
|
+
ScreenField,
|
|
24
|
+
ScreenRecord,
|
|
25
|
+
} from './types.js';
|
|
26
|
+
import { formatScreenValue, shortScreenId } from './values.js';
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
definition: ScreenCollectionDefinition;
|
|
30
|
+
model: RecordHeaderModel;
|
|
31
|
+
record: ScreenRecord;
|
|
32
|
+
relations?: readonly ScreenRelation[];
|
|
33
|
+
hrefFor?: (field: ScreenField, id: string) => string | undefined;
|
|
34
|
+
labels?: Readonly<Record<string, (id: string) => string | undefined>>;
|
|
35
|
+
currency?: string;
|
|
36
|
+
locale?: string;
|
|
37
|
+
backLabel?: string;
|
|
38
|
+
deleting?: boolean;
|
|
39
|
+
statusTones?: Readonly<Record<string, RecordStatusTone>>;
|
|
40
|
+
onback?: () => void;
|
|
41
|
+
onedit?: () => void;
|
|
42
|
+
ondelete?: () => void | Promise<void>;
|
|
43
|
+
extraActions?: Snippet;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let {
|
|
47
|
+
definition,
|
|
48
|
+
model,
|
|
49
|
+
record,
|
|
50
|
+
relations = [],
|
|
51
|
+
hrefFor,
|
|
52
|
+
labels,
|
|
53
|
+
currency,
|
|
54
|
+
locale,
|
|
55
|
+
backLabel,
|
|
56
|
+
deleting = false,
|
|
57
|
+
statusTones,
|
|
58
|
+
onback,
|
|
59
|
+
onedit,
|
|
60
|
+
ondelete,
|
|
61
|
+
extraActions,
|
|
62
|
+
}: Props = $props();
|
|
63
|
+
|
|
64
|
+
const { t } = useI18n();
|
|
65
|
+
const titles = $derived(screenTitles(definition));
|
|
66
|
+
const hasMenu = $derived(Boolean(extraActions || ondelete));
|
|
67
|
+
|
|
68
|
+
let menuOpen = $state(false);
|
|
69
|
+
let confirmOpen = $state(false);
|
|
70
|
+
let deleteError = $state(false);
|
|
71
|
+
|
|
72
|
+
async function confirmDelete(): Promise<void> {
|
|
73
|
+
deleteError = false;
|
|
74
|
+
try {
|
|
75
|
+
await ondelete?.();
|
|
76
|
+
confirmOpen = false;
|
|
77
|
+
} catch {
|
|
78
|
+
deleteError = true;
|
|
79
|
+
confirmOpen = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function referenceLabel(field: ScreenField, id: string): string | undefined {
|
|
84
|
+
const target = relations.find(
|
|
85
|
+
(r) => r.kind === 'reference' && r.field === field.name,
|
|
86
|
+
)?.model;
|
|
87
|
+
const label = target ? labels?.[target]?.(id) : undefined;
|
|
88
|
+
return label && label.trim() !== '' ? label : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function factText(fact: RecordFact): string {
|
|
92
|
+
const value = record[fact.field.name];
|
|
93
|
+
if (fact.kind === 'date') {
|
|
94
|
+
return formatScreenValue(
|
|
95
|
+
{ kind: 'datetime', definition: { format: 'date' } },
|
|
96
|
+
value,
|
|
97
|
+
{ locale },
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return formatScreenValue(fact.field, value, { currency, locale });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function contactHref(fact: RecordFact, text: string): string | undefined {
|
|
104
|
+
if (fact.field.kind === 'email') return `mailto:${text}`;
|
|
105
|
+
if (fact.field.kind === 'tel') return `tel:${text.replace(/[^\d+]/g, '')}`;
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<header class="smrt-record-header" data-smrt-component="RecordHeader">
|
|
111
|
+
{#if onback}
|
|
112
|
+
<Button variant="ghost" size="sm" onclick={onback}>
|
|
113
|
+
{backLabel ?? t(M['ui.screens.back'], { name: titles.plural.toLowerCase() })}
|
|
114
|
+
</Button>
|
|
115
|
+
{/if}
|
|
116
|
+
<div class="smrt-record-header__row">
|
|
117
|
+
<div class="smrt-record-header__identity">
|
|
118
|
+
<h1 class="smrt-record-header__title">
|
|
119
|
+
{#if model.headingIdentifier && model.identifier}
|
|
120
|
+
{model.heading.slice(0, model.heading.length - model.identifier.value.length)}<code
|
|
121
|
+
class="smrt-record-header__id">{model.identifier.value}</code
|
|
122
|
+
>
|
|
123
|
+
{:else}
|
|
124
|
+
{model.heading}
|
|
125
|
+
{/if}
|
|
126
|
+
</h1>
|
|
127
|
+
{#if model.identifier && !model.headingIdentifier}
|
|
128
|
+
<code class="smrt-record-header__id smrt-record-header__id--chip" title={model.identifier.field.label}
|
|
129
|
+
>{model.identifier.value}</code
|
|
130
|
+
>
|
|
131
|
+
{/if}
|
|
132
|
+
{#if model.status}
|
|
133
|
+
<Badge variant={statusTone(model.status.value, statusTones)}>
|
|
134
|
+
<span class="smrt-record-header__sr">{model.status.field.label}: </span>{statusLabel(model.status.value)}
|
|
135
|
+
</Badge>
|
|
136
|
+
{/if}
|
|
137
|
+
</div>
|
|
138
|
+
<div class="smrt-record-header__actions">
|
|
139
|
+
{#if onedit}
|
|
140
|
+
<Button variant="primary" onclick={onedit}>{t(M['ui.screens.edit'])}</Button>
|
|
141
|
+
{/if}
|
|
142
|
+
{#if hasMenu}
|
|
143
|
+
<Popover
|
|
144
|
+
label={t(M['ui.screens.more_actions'])}
|
|
145
|
+
triggerLabel={t(M['ui.screens.more_actions'])}
|
|
146
|
+
variant="icon"
|
|
147
|
+
placement="bottom-end"
|
|
148
|
+
bind:open={menuOpen}
|
|
149
|
+
>
|
|
150
|
+
{#snippet trigger()}<span aria-hidden="true">⋯</span>{/snippet}
|
|
151
|
+
<div class="smrt-record-header__menu">
|
|
152
|
+
{#if extraActions}{@render extraActions()}{/if}
|
|
153
|
+
{#if ondelete}
|
|
154
|
+
<Button
|
|
155
|
+
variant="danger"
|
|
156
|
+
onclick={() => {
|
|
157
|
+
menuOpen = false;
|
|
158
|
+
confirmOpen = true;
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
{t(M['ui.screens.delete'])}
|
|
162
|
+
</Button>
|
|
163
|
+
{/if}
|
|
164
|
+
</div>
|
|
165
|
+
</Popover>
|
|
166
|
+
{/if}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
{#if model.facts.length > 0}
|
|
170
|
+
<dl class="smrt-record-header__facts">
|
|
171
|
+
{#each model.facts as fact (fact.field.name)}
|
|
172
|
+
{@const text = factText(fact)}
|
|
173
|
+
<div class="smrt-record-header__fact">
|
|
174
|
+
<dt>{fact.field.label}</dt>
|
|
175
|
+
<dd>
|
|
176
|
+
{#if fact.kind === 'party'}
|
|
177
|
+
{@const href = hrefFor?.(fact.field, text)}
|
|
178
|
+
{@const label = referenceLabel(fact.field, text)}
|
|
179
|
+
{#if href}
|
|
180
|
+
<a {href}>{#if label}{label}{:else}<code title={text}>{shortScreenId(text)}</code>{/if}</a>
|
|
181
|
+
{:else if label}
|
|
182
|
+
{label}
|
|
183
|
+
{:else}
|
|
184
|
+
<code title={text}>{shortScreenId(text)}</code>
|
|
185
|
+
{/if}
|
|
186
|
+
{:else if fact.kind === 'contact' && contactHref(fact, text)}
|
|
187
|
+
<a href={contactHref(fact, text)}>{text}</a>
|
|
188
|
+
{:else}
|
|
189
|
+
{text}
|
|
190
|
+
{/if}
|
|
191
|
+
</dd>
|
|
192
|
+
</div>
|
|
193
|
+
{/each}
|
|
194
|
+
</dl>
|
|
195
|
+
{/if}
|
|
196
|
+
{#if deleteError}
|
|
197
|
+
<Alert variant="error" dismissible ondismiss={() => (deleteError = false)}>
|
|
198
|
+
{t(M['ui.screens.delete_failed'])}
|
|
199
|
+
</Alert>
|
|
200
|
+
{/if}
|
|
201
|
+
</header>
|
|
202
|
+
|
|
203
|
+
{#if ondelete}
|
|
204
|
+
<ConfirmDialog
|
|
205
|
+
open={confirmOpen}
|
|
206
|
+
title={t(M['ui.screens.delete_title'], { name: titles.singular.toLowerCase() })}
|
|
207
|
+
message={t(M['ui.screens.delete_message'])}
|
|
208
|
+
confirmLabel={t(M['ui.screens.delete'])}
|
|
209
|
+
cancelLabel={t(M['ui.screens.cancel'])}
|
|
210
|
+
destructive
|
|
211
|
+
loading={deleting}
|
|
212
|
+
onconfirm={confirmDelete}
|
|
213
|
+
oncancel={() => (confirmOpen = false)}
|
|
214
|
+
/>
|
|
215
|
+
{/if}
|
|
216
|
+
|
|
217
|
+
<style>
|
|
218
|
+
@layer smrt.reset, smrt.structure, smrt.theme;
|
|
219
|
+
@layer smrt.theme {
|
|
220
|
+
.smrt-record-header { display: grid; gap: var(--smrt-spacing-2); justify-items: start; }
|
|
221
|
+
.smrt-record-header__row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--smrt-spacing-3); inline-size: 100%; }
|
|
222
|
+
.smrt-record-header__identity { display: flex; flex-wrap: wrap; align-items: center; gap: var(--smrt-spacing-2) var(--smrt-spacing-3); min-inline-size: 0; }
|
|
223
|
+
.smrt-record-header__title { margin: 0; font-size: var(--smrt-typography-headline-small-size, 1.5rem); font-weight: var(--smrt-typography-weight-semibold); overflow-wrap: anywhere; }
|
|
224
|
+
.smrt-record-header__id { font-family: var(--smrt-font-family-mono); }
|
|
225
|
+
.smrt-record-header__id--chip { padding: 0 var(--smrt-spacing-2); border-radius: var(--smrt-radius-sm); background: var(--smrt-color-surface-container); color: var(--smrt-color-on-surface-variant); }
|
|
226
|
+
.smrt-record-header__actions { display: flex; align-items: center; gap: var(--smrt-spacing-2); }
|
|
227
|
+
.smrt-record-header__menu { display: grid; gap: var(--smrt-spacing-2); padding: var(--smrt-spacing-2); min-inline-size: 10rem; }
|
|
228
|
+
.smrt-record-header__facts { display: flex; flex-wrap: wrap; gap: var(--smrt-spacing-2) var(--smrt-spacing-6); margin: 0; }
|
|
229
|
+
.smrt-record-header__fact { display: grid; gap: 0; }
|
|
230
|
+
.smrt-record-header__fact dt { color: var(--smrt-color-on-surface-variant); font-size: var(--smrt-typography-body-small-size, 0.75rem); }
|
|
231
|
+
.smrt-record-header__fact dd { margin: 0; font-variant-numeric: tabular-nums; }
|
|
232
|
+
.smrt-record-header__sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
233
|
+
@media (max-width: 47.99rem) {
|
|
234
|
+
.smrt-record-header__actions { inline-size: 100%; }
|
|
235
|
+
.smrt-record-header__actions > :global(:first-child) { flex: 1; }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { RecordStatusTone } from '../overview/widgets/data.js';
|
|
3
|
+
import type { RecordHeaderModel } from './record-header.js';
|
|
4
|
+
import type { ScreenRelation } from './relations.js';
|
|
5
|
+
import type { ScreenCollectionDefinition, ScreenField, ScreenRecord } from './types.js';
|
|
6
|
+
interface Props {
|
|
7
|
+
definition: ScreenCollectionDefinition;
|
|
8
|
+
model: RecordHeaderModel;
|
|
9
|
+
record: ScreenRecord;
|
|
10
|
+
relations?: readonly ScreenRelation[];
|
|
11
|
+
hrefFor?: (field: ScreenField, id: string) => string | undefined;
|
|
12
|
+
labels?: Readonly<Record<string, (id: string) => string | undefined>>;
|
|
13
|
+
currency?: string;
|
|
14
|
+
locale?: string;
|
|
15
|
+
backLabel?: string;
|
|
16
|
+
deleting?: boolean;
|
|
17
|
+
statusTones?: Readonly<Record<string, RecordStatusTone>>;
|
|
18
|
+
onback?: () => void;
|
|
19
|
+
onedit?: () => void;
|
|
20
|
+
ondelete?: () => void | Promise<void>;
|
|
21
|
+
extraActions?: Snippet;
|
|
22
|
+
}
|
|
23
|
+
declare const RecordHeader: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type RecordHeader = ReturnType<typeof RecordHeader>;
|
|
25
|
+
export default RecordHeader;
|
|
26
|
+
//# sourceMappingURL=RecordHeader.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecordHeader.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/screens/RecordHeader.svelte.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAGpE,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EACV,0BAA0B,EAC1B,WAAW,EACX,YAAY,EACb,MAAM,YAAY,CAAC;AAIpB,UAAU,KAAK;IACb,UAAU,EAAE,0BAA0B,CAAC;IACvC,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACjE,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACzD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAyKD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|