@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.
Files changed (85) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +2 -2
  3. package/agents/overview-surfaces.md +108 -4
  4. package/agents/screens.md +171 -5
  5. package/dist/components/overview/index.d.ts +4 -1
  6. package/dist/components/overview/index.d.ts.map +1 -1
  7. package/dist/components/overview/index.js +3 -0
  8. package/dist/components/overview/record.d.ts +51 -0
  9. package/dist/components/overview/record.d.ts.map +1 -0
  10. package/dist/components/overview/record.js +146 -0
  11. package/dist/components/overview/server.d.ts +4 -0
  12. package/dist/components/overview/server.d.ts.map +1 -1
  13. package/dist/components/overview/server.js +4 -0
  14. package/dist/components/overview/types.d.ts +12 -0
  15. package/dist/components/overview/types.d.ts.map +1 -1
  16. package/dist/components/overview/widgets/LineItemsWidget.svelte +144 -0
  17. package/dist/components/overview/widgets/LineItemsWidget.svelte.d.ts +6 -0
  18. package/dist/components/overview/widgets/LineItemsWidget.svelte.d.ts.map +1 -0
  19. package/dist/components/overview/widgets/RecordListWidget.svelte +67 -3
  20. package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts.map +1 -1
  21. package/dist/components/overview/widgets/RecordTable.svelte +167 -0
  22. package/dist/components/overview/widgets/RecordTable.svelte.d.ts +21 -0
  23. package/dist/components/overview/widgets/RecordTable.svelte.d.ts.map +1 -0
  24. package/dist/components/overview/widgets/RelatedWidget.svelte +117 -0
  25. package/dist/components/overview/widgets/RelatedWidget.svelte.d.ts +6 -0
  26. package/dist/components/overview/widgets/RelatedWidget.svelte.d.ts.map +1 -0
  27. package/dist/components/overview/widgets/core.d.ts +13 -2
  28. package/dist/components/overview/widgets/core.d.ts.map +1 -1
  29. package/dist/components/overview/widgets/core.js +43 -2
  30. package/dist/components/overview/widgets/data.d.ts +97 -8
  31. package/dist/components/overview/widgets/data.d.ts.map +1 -1
  32. package/dist/components/overview/widgets/line-totals.d.ts +48 -0
  33. package/dist/components/overview/widgets/line-totals.d.ts.map +1 -0
  34. package/dist/components/overview/widgets/line-totals.js +91 -0
  35. package/dist/components/overview/widgets/record-loaders.d.ts +63 -0
  36. package/dist/components/overview/widgets/record-loaders.d.ts.map +1 -0
  37. package/dist/components/overview/widgets/record-loaders.js +150 -0
  38. package/dist/components/overview/widgets/record-table.d.ts +45 -0
  39. package/dist/components/overview/widgets/record-table.d.ts.map +1 -0
  40. package/dist/components/overview/widgets/record-table.js +201 -0
  41. package/dist/components/overview/widgets/record-widgets.d.ts +27 -0
  42. package/dist/components/overview/widgets/record-widgets.d.ts.map +1 -0
  43. package/dist/components/overview/widgets/record-widgets.js +100 -0
  44. package/dist/components/screens/DetailScreen.svelte +105 -6
  45. package/dist/components/screens/DetailScreen.svelte.d.ts.map +1 -1
  46. package/dist/components/screens/EditForm.svelte +39 -3
  47. package/dist/components/screens/EditForm.svelte.d.ts.map +1 -1
  48. package/dist/components/screens/RecipeScreens.svelte +18 -18
  49. package/dist/components/screens/RecipeScreens.svelte.d.ts.map +1 -1
  50. package/dist/components/screens/RecordHeader.svelte +238 -0
  51. package/dist/components/screens/RecordHeader.svelte.d.ts +26 -0
  52. package/dist/components/screens/RecordHeader.svelte.d.ts.map +1 -0
  53. package/dist/components/screens/RecordScreen.svelte +511 -0
  54. package/dist/components/screens/RecordScreen.svelte.d.ts +5 -0
  55. package/dist/components/screens/RecordScreen.svelte.d.ts.map +1 -0
  56. package/dist/components/screens/columns.d.ts +29 -0
  57. package/dist/components/screens/columns.d.ts.map +1 -0
  58. package/dist/components/screens/columns.js +115 -0
  59. package/dist/components/screens/fields.d.ts +12 -2
  60. package/dist/components/screens/fields.d.ts.map +1 -1
  61. package/dist/components/screens/fields.js +37 -5
  62. package/dist/components/screens/index.d.ts +7 -2
  63. package/dist/components/screens/index.d.ts.map +1 -1
  64. package/dist/components/screens/index.js +6 -1
  65. package/dist/components/screens/record-header.d.ts +51 -0
  66. package/dist/components/screens/record-header.d.ts.map +1 -0
  67. package/dist/components/screens/record-header.js +118 -0
  68. package/dist/components/screens/relations.d.ts +49 -0
  69. package/dist/components/screens/relations.d.ts.map +1 -0
  70. package/dist/components/screens/relations.js +98 -0
  71. package/dist/components/screens/roles.d.ts +56 -0
  72. package/dist/components/screens/roles.d.ts.map +1 -0
  73. package/dist/components/screens/roles.js +174 -0
  74. package/dist/components/screens/types.d.ts +182 -1
  75. package/dist/components/screens/types.d.ts.map +1 -1
  76. package/dist/components/screens/values.d.ts +23 -1
  77. package/dist/components/screens/values.d.ts.map +1 -1
  78. package/dist/components/screens/values.js +80 -4
  79. package/dist/i18n/strings.overview.d.ts +25 -0
  80. package/dist/i18n/strings.overview.d.ts.map +1 -1
  81. package/dist/i18n/strings.overview.js +25 -0
  82. package/dist/i18n/strings.screens.d.ts +8 -0
  83. package/dist/i18n/strings.screens.d.ts.map +1 -1
  84. package/dist/i18n/strings.screens.js +8 -0
  85. 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"}