@happyvertical/smrt-fields 0.40.60 → 0.40.62
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 +58 -12
- package/README.md +143 -42
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/cache.d.ts +18 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/chunks/FieldPolicyCollection--RxukfCX.js +1218 -0
- package/dist/chunks/FieldPolicyCollection--RxukfCX.js.map +1 -0
- package/dist/collections/FieldPolicyCollection.d.ts +95 -0
- package/dist/collections/FieldPolicyCollection.d.ts.map +1 -0
- package/dist/collections/FieldPolicySuggestionCollection.d.ts +160 -0
- package/dist/collections/FieldPolicySuggestionCollection.d.ts.map +1 -0
- package/dist/collections/FieldUsageCounterCollection.d.ts +143 -0
- package/dist/collections/FieldUsageCounterCollection.d.ts.map +1 -0
- package/dist/deterministic-id.d.ts +18 -0
- package/dist/deterministic-id.d.ts.map +1 -0
- package/dist/field-definitions.d.ts +85 -0
- package/dist/field-definitions.d.ts.map +1 -0
- package/dist/field-policy-resolver.d.ts +46 -0
- package/dist/field-policy-resolver.d.ts.map +1 -0
- package/dist/index.d.ts +15 -532
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1813 -628
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1487 -191
- package/dist/models/FieldPolicy.d.ts +163 -0
- package/dist/models/FieldPolicy.d.ts.map +1 -0
- package/dist/models/FieldPolicySuggestion.d.ts +132 -0
- package/dist/models/FieldPolicySuggestion.d.ts.map +1 -0
- package/dist/models/FieldUsageCounter.d.ts +178 -0
- package/dist/models/FieldUsageCounter.d.ts.map +1 -0
- package/dist/models/FieldUsageReportReceipt.d.ts +25 -0
- package/dist/models/FieldUsageReportReceipt.d.ts.map +1 -0
- package/dist/permissions.d.ts +9 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +39 -0
- package/dist/playground.js.map +1 -0
- package/dist/settings-catalog.d.ts +73 -0
- package/dist/settings-catalog.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +449 -9
- package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +277 -0
- package/dist/svelte/__tests__/FieldPolicyGear.test.js +503 -0
- package/dist/svelte/__tests__/FormHelp.test.js +138 -0
- package/dist/svelte/__tests__/ModeUX.test.js +109 -0
- package/dist/svelte/__tests__/ObjectForm.test.js +687 -0
- package/dist/svelte/__tests__/ObjectFormGeneratedApi.integration.test.js +436 -0
- package/dist/svelte/__tests__/PolicyField.test.js +373 -0
- package/dist/svelte/__tests__/UsageLearning.test.js +140 -0
- package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte +30 -0
- package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts +9 -0
- package/dist/svelte/__tests__/fixtures/AdvancedFieldsFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte +33 -0
- package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/DomPrefillFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte +19 -0
- package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/FormHelpFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte +18 -0
- package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts +11 -0
- package/dist/svelte/__tests__/fixtures/GeneratedApiGearFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte +11 -0
- package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts +19 -0
- package/dist/svelte/__tests__/fixtures/GracefulDegradeFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte +18 -0
- package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts +8 -0
- package/dist/svelte/__tests__/fixtures/ModeSwitchFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte +24 -0
- package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts +13 -0
- package/dist/svelte/__tests__/fixtures/NoIdFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte +37 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +15 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte +17 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte +25 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormRegistryFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte +34 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSnippetFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte +29 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts +11 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte +33 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts +9 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormSourceSnippetFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte +40 -0
- package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts +8 -0
- package/dist/svelte/__tests__/fixtures/PolicyDataTableFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte +26 -0
- package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts +13 -0
- package/dist/svelte/__tests__/fixtures/PrefillFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte +39 -0
- package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts +17 -0
- package/dist/svelte/__tests__/fixtures/ProviderFieldFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/RegistryInput.svelte +15 -0
- package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts +5 -0
- package/dist/svelte/__tests__/fixtures/RegistryInput.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte +29 -0
- package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts +14 -0
- package/dist/svelte/__tests__/fixtures/RevealPrefillFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte +24 -0
- package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts +14 -0
- package/dist/svelte/__tests__/fixtures/SettingsCatalogStub.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte +28 -0
- package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts +13 -0
- package/dist/svelte/__tests__/fixtures/SnippetFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte +23 -0
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts +10 -0
- package/dist/svelte/__tests__/fixtures/SuggestionGearFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte +14 -0
- package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts +19 -0
- package/dist/svelte/__tests__/fixtures/ToggleModeButton.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte +25 -0
- package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts +14 -0
- package/dist/svelte/__tests__/fixtures/TooltipFixture.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/settings-catalog.test.js +161 -0
- package/dist/svelte/components/AdvancedFields.svelte +49 -0
- package/dist/svelte/components/AdvancedFields.svelte.d.ts +17 -0
- package/dist/svelte/components/AdvancedFields.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldInput.svelte +159 -0
- package/dist/svelte/components/FieldInput.svelte.d.ts +5 -0
- package/dist/svelte/components/FieldInput.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte +288 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +29 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyEditor.svelte +417 -0
- package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts +18 -0
- package/dist/svelte/components/FieldPolicyEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyGearButton.svelte +24 -0
- package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts +8 -0
- package/dist/svelte/components/FieldPolicyGearButton.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte +169 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +20 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicyProvider.svelte +89 -0
- package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts +35 -0
- package/dist/svelte/components/FieldPolicyProvider.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte +135 -0
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts +11 -0
- package/dist/svelte/components/FieldPolicySuggestionQueue.svelte.d.ts.map +1 -0
- package/dist/svelte/components/FormHelp.svelte +157 -0
- package/dist/svelte/components/FormHelp.svelte.d.ts +15 -0
- package/dist/svelte/components/FormHelp.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ModeSwitch.svelte +47 -0
- package/dist/svelte/components/ModeSwitch.svelte.d.ts +10 -0
- package/dist/svelte/components/ModeSwitch.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ObjectForm.svelte +440 -0
- package/dist/svelte/components/ObjectForm.svelte.d.ts +50 -0
- package/dist/svelte/components/ObjectForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ObjectFormSourceProvider.svelte +18 -0
- package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts +11 -0
- package/dist/svelte/components/ObjectFormSourceProvider.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PolicyField.svelte +338 -0
- package/dist/svelte/components/PolicyField.svelte.d.ts +80 -0
- package/dist/svelte/components/PolicyField.svelte.d.ts.map +1 -0
- package/dist/svelte/context.svelte.d.ts +54 -0
- package/dist/svelte/context.svelte.d.ts.map +1 -0
- package/dist/svelte/context.svelte.js +55 -0
- package/dist/svelte/data-table.d.ts +17 -0
- package/dist/svelte/data-table.d.ts.map +1 -0
- package/dist/svelte/data-table.js +19 -0
- package/dist/svelte/field-policy-editor.d.ts +81 -0
- package/dist/svelte/field-policy-editor.d.ts.map +1 -0
- package/dist/svelte/field-policy-editor.js +120 -0
- package/dist/svelte/gear-context.svelte.d.ts +16 -0
- package/dist/svelte/gear-context.svelte.d.ts.map +1 -0
- package/dist/svelte/gear-context.svelte.js +15 -0
- package/dist/svelte/index.d.ts +63 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +41 -0
- package/dist/svelte/input-registry.d.ts +21 -0
- package/dist/svelte/input-registry.d.ts.map +1 -0
- package/dist/svelte/input-registry.js +26 -0
- package/dist/svelte/object-form-source-context.svelte.d.ts +4 -0
- package/dist/svelte/object-form-source-context.svelte.d.ts.map +1 -0
- package/dist/svelte/object-form-source-context.svelte.js +8 -0
- package/dist/svelte/object-form-source.svelte.d.ts +18 -0
- package/dist/svelte/object-form-source.svelte.d.ts.map +1 -0
- package/dist/svelte/object-form-source.svelte.js +99 -0
- package/dist/svelte/object-form.d.ts +6 -0
- package/dist/svelte/object-form.d.ts.map +1 -0
- package/dist/svelte/object-form.js +38 -0
- package/dist/svelte/playground/FieldPolicyFormPreview.svelte +212 -0
- package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/FieldPolicyFormPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground/ObjectFormPreview.svelte +129 -0
- package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/ObjectFormPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts +20 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +38 -0
- package/dist/svelte/settings-catalog.d.ts +54 -0
- package/dist/svelte/settings-catalog.d.ts.map +1 -0
- package/dist/svelte/settings-catalog.js +116 -0
- package/dist/svelte/suggestions.d.ts +38 -0
- package/dist/svelte/suggestions.d.ts.map +1 -0
- package/dist/svelte/suggestions.js +68 -0
- package/dist/svelte/types.d.ts +101 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +4 -0
- package/dist/svelte/usage-capture.d.ts +63 -0
- package/dist/svelte/usage-capture.d.ts.map +1 -0
- package/dist/svelte/usage-capture.js +61 -0
- package/dist/types.d.ts +316 -203
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/dist/usage-learning.d.ts +201 -0
- package/dist/usage-learning.d.ts.map +1 -0
- package/dist/usage-schedules.d.ts +110 -0
- package/dist/usage-schedules.d.ts.map +1 -0
- package/dist/users-module.d.ts +30 -0
- package/dist/users-module.d.ts.map +1 -0
- package/package.json +33 -8
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* PolicyField — layout-neutral wrapper around any input.
|
|
4
|
+
*
|
|
5
|
+
* Contributes:
|
|
6
|
+
* - Visibility by resolved tier × current mode (advanced fields hidden in
|
|
7
|
+
* basic mode)
|
|
8
|
+
* - Initial-value prefill from the resolved default (new records only —
|
|
9
|
+
* never clobbers loaded values)
|
|
10
|
+
* - Label rendering (the resolved label; when the policy has no label the
|
|
11
|
+
* label element is omitted rather than fabricated from the raw field name,
|
|
12
|
+
* and tooltip-density help falls back to a visible hint)
|
|
13
|
+
* - Help hint rendered from resolved help (manifest description → org/user
|
|
14
|
+
* override)
|
|
15
|
+
* - Required marker
|
|
16
|
+
*
|
|
17
|
+
* Label ↔ control association: the default wrapper renders
|
|
18
|
+
* `<label for={name}>`, so the wrapped control should carry an `id` equal to
|
|
19
|
+
* the field `name`. When the first wrapped control has a different `id`, the
|
|
20
|
+
* `for` attribute is re-pointed to it after mount; when it has no `id` at
|
|
21
|
+
* all, the wrapper assigns `id={name}` so the association is guaranteed.
|
|
22
|
+
*
|
|
23
|
+
* Headless escape hatch: pass a `render` snippet receiving
|
|
24
|
+
* `{visible, label, help, defaultValue, required, tier}` for fully custom
|
|
25
|
+
* rendering. Without a snippet, PolicyField wraps children in a label + help
|
|
26
|
+
* affordance and controls visibility.
|
|
27
|
+
*
|
|
28
|
+
* Graceful degradation: outside a FieldPolicyProvider, PolicyField renders its
|
|
29
|
+
* children verbatim (no visibility filtering, no label/help injection) so
|
|
30
|
+
* forms can adopt it incrementally.
|
|
31
|
+
*/
|
|
32
|
+
import type { Snippet } from 'svelte';
|
|
33
|
+
import { tryGetFieldPolicyContext } from '../context.svelte.js';
|
|
34
|
+
import type { PolicyFieldSnippetProps } from '../types.js';
|
|
35
|
+
|
|
36
|
+
export interface Props {
|
|
37
|
+
/** The field name matching a key in the resolved policy's `fields`. */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Stable per-form control id; defaults to `name` for standalone use. */
|
|
40
|
+
id?: string;
|
|
41
|
+
/** Stable per-form help id; defaults to `${id}-help`. */
|
|
42
|
+
helpId?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Children — the actual input(s). Always rendered when the field is visible
|
|
45
|
+
* (unless the `render` snippet escape hatch is used).
|
|
46
|
+
*/
|
|
47
|
+
children?: Snippet;
|
|
48
|
+
/**
|
|
49
|
+
* Headless escape hatch: a snippet receiving all resolved policy data for
|
|
50
|
+
* fully custom rendering. When provided, it replaces the default label +
|
|
51
|
+
* children + help layout entirely.
|
|
52
|
+
*/
|
|
53
|
+
render?: Snippet<[PolicyFieldSnippetProps]>;
|
|
54
|
+
/**
|
|
55
|
+
* Whether this is a new record. When true (default) and the field has a
|
|
56
|
+
* resolved default, the provider can prefill. Set to false for loaded
|
|
57
|
+
* records to prevent overwriting existing values.
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
isNewRecord?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Override the resolved label. When provided, takes precedence over the
|
|
63
|
+
* policy label.
|
|
64
|
+
*/
|
|
65
|
+
label?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Override the resolved help text. When provided, takes precedence over
|
|
68
|
+
* the policy help.
|
|
69
|
+
*/
|
|
70
|
+
help?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Density of the help hint: 'hint' (inline hint text, default) or
|
|
73
|
+
* 'tooltip' (title attribute on the label).
|
|
74
|
+
* @default 'hint'
|
|
75
|
+
*/
|
|
76
|
+
helpDensity?: 'hint' | 'tooltip';
|
|
77
|
+
/** Additional class for the wrapper element. */
|
|
78
|
+
class?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let {
|
|
82
|
+
name,
|
|
83
|
+
id = name,
|
|
84
|
+
helpId = `${id}-help`,
|
|
85
|
+
children,
|
|
86
|
+
render: renderSnippet,
|
|
87
|
+
isNewRecord = true,
|
|
88
|
+
label: labelOverride,
|
|
89
|
+
help: helpOverride,
|
|
90
|
+
helpDensity = 'hint',
|
|
91
|
+
class: className = '',
|
|
92
|
+
}: Props = $props();
|
|
93
|
+
|
|
94
|
+
const context = tryGetFieldPolicyContext();
|
|
95
|
+
|
|
96
|
+
// Reactive resolved field policy — undefined when not in a Provider or when
|
|
97
|
+
// the field name is not in the resolved set.
|
|
98
|
+
const resolvedField = $derived(context?.getField(name));
|
|
99
|
+
|
|
100
|
+
const tier = $derived(resolvedField?.visibility ?? 'basic');
|
|
101
|
+
|
|
102
|
+
// A field is visible when:
|
|
103
|
+
// - No context (graceful degradation → always visible)
|
|
104
|
+
// - Tier is 'basic'
|
|
105
|
+
// - Tier is 'advanced' and mode is 'advanced'
|
|
106
|
+
// - Tier is 'hidden' → never visible (but required-and-defaultless fields are
|
|
107
|
+
// forced basic by the resolver, so this is a safety net)
|
|
108
|
+
const visible = $derived.by(() => {
|
|
109
|
+
if (!context) return true;
|
|
110
|
+
if (tier === 'hidden') return false;
|
|
111
|
+
if (tier === 'basic') return true;
|
|
112
|
+
// advanced
|
|
113
|
+
return context.mode.current === 'advanced';
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const label = $derived(labelOverride ?? resolvedField?.label ?? null);
|
|
117
|
+
|
|
118
|
+
const help = $derived(helpOverride ?? resolvedField?.help ?? null);
|
|
119
|
+
|
|
120
|
+
const required = $derived(resolvedField?.required ?? false);
|
|
121
|
+
|
|
122
|
+
const hasDefault = $derived(resolvedField?.hasDefault ?? false);
|
|
123
|
+
|
|
124
|
+
const defaultValue = $derived(resolvedField?.defaultValue);
|
|
125
|
+
|
|
126
|
+
// Snippet escape hatch data
|
|
127
|
+
const snippetProps = $derived<PolicyFieldSnippetProps>({
|
|
128
|
+
id,
|
|
129
|
+
helpId,
|
|
130
|
+
visible,
|
|
131
|
+
label,
|
|
132
|
+
help,
|
|
133
|
+
defaultValue,
|
|
134
|
+
required,
|
|
135
|
+
tier,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// --- Default prefill (new records only) ---
|
|
139
|
+
// When this is a new record and the field has a resolved default, prefill the
|
|
140
|
+
// wrapped input's value — but ONLY when the input is currently empty (a loaded
|
|
141
|
+
// record with an existing value is never clobbered). Mirrors the "defaults
|
|
142
|
+
// only apply to new records" invariant from the resolver.
|
|
143
|
+
//
|
|
144
|
+
// Runs via $effect rather than onMount so it also covers advanced-tier fields
|
|
145
|
+
// that are hidden at mount and revealed later by the mode switch: the wrapper
|
|
146
|
+
// div (and rootEl) only exist once the field is visible, and the effect re-runs
|
|
147
|
+
// when visibility flips. A one-shot `prefilled` guard prevents re-filling an
|
|
148
|
+
// input the user has deliberately cleared.
|
|
149
|
+
//
|
|
150
|
+
// After writing a value we dispatch the event the matching binding listens to
|
|
151
|
+
// (`input` for text-like controls, `change` for select/checkbox/radio) so a
|
|
152
|
+
// consumer's `bind:value` variable picks the prefilled value up instead of
|
|
153
|
+
// staying stale.
|
|
154
|
+
let rootEl = $state<HTMLDivElement | null>(null);
|
|
155
|
+
let prefilled = false;
|
|
156
|
+
|
|
157
|
+
function prefillInputs(): void {
|
|
158
|
+
if (!rootEl) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const inputs = rootEl.querySelectorAll<
|
|
163
|
+
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
164
|
+
>('input, textarea, select');
|
|
165
|
+
|
|
166
|
+
for (const input of inputs) {
|
|
167
|
+
if (input instanceof HTMLSelectElement) {
|
|
168
|
+
if (input.value === '') {
|
|
169
|
+
input.value = String(defaultValue ?? '');
|
|
170
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const inputType = (input as HTMLInputElement).type;
|
|
176
|
+
if (inputType === 'checkbox') {
|
|
177
|
+
const checkbox = input as HTMLInputElement;
|
|
178
|
+
if (!checkbox.checked && defaultValue === true) {
|
|
179
|
+
checkbox.checked = true;
|
|
180
|
+
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (inputType === 'radio') {
|
|
186
|
+
const radio = input as HTMLInputElement;
|
|
187
|
+
if (String(defaultValue) === radio.value && !radio.checked) {
|
|
188
|
+
radio.checked = true;
|
|
189
|
+
radio.dispatchEvent(new Event('change', { bubbles: true }));
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// text / textarea / number / date / etc.
|
|
195
|
+
const textInput = input as HTMLInputElement | HTMLTextAreaElement;
|
|
196
|
+
if (textInput.value === '') {
|
|
197
|
+
textInput.value = String(defaultValue ?? '');
|
|
198
|
+
textInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Link the help hint to the first wrapped control programmatically. Only
|
|
204
|
+
// when the hint is actually rendered as a paragraph (hint density, or tooltip
|
|
205
|
+
// density with no label) — otherwise the aria-describedby target would not
|
|
206
|
+
// exist. Respects a consumer-provided aria-describedby and only fills in the
|
|
207
|
+
// missing association. Runs after the wrapper exists, so it also covers
|
|
208
|
+
// advanced-tier fields revealed later by the mode switch.
|
|
209
|
+
const hintRendered = $derived(
|
|
210
|
+
help !== null && (helpDensity === 'hint' || label === null),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
$effect(() => {
|
|
214
|
+
if (!rootEl || !hintRendered) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const control = rootEl.querySelector<HTMLElement>('input, textarea, select');
|
|
218
|
+
if (!control) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (control.hasAttribute('aria-describedby')) {
|
|
222
|
+
return; // Consumer owns the association — never overwrite it.
|
|
223
|
+
}
|
|
224
|
+
control.setAttribute('aria-describedby', helpId);
|
|
225
|
+
return () => {
|
|
226
|
+
// Hint no longer rendered (or wrapper torn down) — drop the link we added.
|
|
227
|
+
if (control.getAttribute('aria-describedby') === helpId) {
|
|
228
|
+
control.removeAttribute('aria-describedby');
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// --- Label ↔ control association (default wrapper only) ---
|
|
234
|
+
// The wrapper renders `<label for={name}>`, which only associates with the
|
|
235
|
+
// wrapped control when that control's `id` matches. Consumers normally give
|
|
236
|
+
// the input `id={name}`; this effect repairs the association once the wrapper
|
|
237
|
+
// exists (covering advanced-tier fields revealed later by the mode switch):
|
|
238
|
+
// - control with its own id → re-point `for` at that id;
|
|
239
|
+
// - control with no id → assign `id={name}` so the association holds;
|
|
240
|
+
// - no wrapped control at all → omit `for` (a dangling reference is worse
|
|
241
|
+
// than none).
|
|
242
|
+
//
|
|
243
|
+
// `controlId` is undefined until the first effect pass (SSR and first paint
|
|
244
|
+
// keep the documented `for={name}` convention); afterwards it is the repaired
|
|
245
|
+
// target id, or null when there is no wrapped control.
|
|
246
|
+
let controlId = $state<string | null | undefined>(undefined);
|
|
247
|
+
|
|
248
|
+
$effect(() => {
|
|
249
|
+
if (!rootEl || label === null || !visible) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// Form controls only — buttons and contenteditable hosts are out of scope
|
|
253
|
+
// (same scope as the aria-describedby effect above).
|
|
254
|
+
const control = rootEl.querySelector<HTMLElement>('input, textarea, select');
|
|
255
|
+
if (!control) {
|
|
256
|
+
controlId = null;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (control.id) {
|
|
260
|
+
controlId = control.id;
|
|
261
|
+
} else {
|
|
262
|
+
control.setAttribute('id', id);
|
|
263
|
+
controlId = id;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Prefill is a one-shot per component instance: it runs the first time the
|
|
268
|
+
// wrapper exists with a usable default (mount for visible fields, or first
|
|
269
|
+
// reveal for advanced-tier fields hidden in basic mode). The `visible` read
|
|
270
|
+
// makes the effect re-run when a hidden field becomes visible; the `prefilled`
|
|
271
|
+
// guard keeps it one-shot so a user who clears the input is never re-filled.
|
|
272
|
+
$effect(() => {
|
|
273
|
+
if (prefilled || !visible || !isNewRecord || !hasDefault) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (defaultValue === undefined || defaultValue === null) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (!rootEl) {
|
|
280
|
+
// Wrapper not mounted yet — rootEl is tracked $state, so the effect
|
|
281
|
+
// re-runs once the binding lands.
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
prefilled = true;
|
|
285
|
+
prefillInputs();
|
|
286
|
+
});
|
|
287
|
+
</script>
|
|
288
|
+
|
|
289
|
+
{#if renderSnippet}
|
|
290
|
+
{@render renderSnippet(snippetProps)}
|
|
291
|
+
{:else if visible}
|
|
292
|
+
<div class="policy-field {className}" bind:this={rootEl}>
|
|
293
|
+
{#if label !== null}
|
|
294
|
+
<!-- `for` tracks the repaired label↔control association: the `name`
|
|
295
|
+
convention before the first effect pass (SSR / first paint), the
|
|
296
|
+
repaired control id afterwards; Svelte omits the attribute entirely
|
|
297
|
+
when it resolves to null (no wrapped control). -->
|
|
298
|
+
<label
|
|
299
|
+
class="policy-field__label"
|
|
300
|
+
for={controlId === undefined ? id : controlId}
|
|
301
|
+
title={helpDensity === 'tooltip' && help !== null ? help : undefined}
|
|
302
|
+
>
|
|
303
|
+
{label}{#if required}<span class="policy-field__required" aria-hidden="true">*</span>{/if}
|
|
304
|
+
</label>
|
|
305
|
+
{/if}
|
|
306
|
+
{@render children?.()}
|
|
307
|
+
{#if help !== null && (helpDensity === 'hint' || label === null)}
|
|
308
|
+
<!-- Hint text density, or tooltip density with no label to attach the
|
|
309
|
+
title attribute to (fall back to visible help rather than dropping
|
|
310
|
+
it). -->
|
|
311
|
+
<p class="policy-field__hint" id={helpId}>{help}</p>
|
|
312
|
+
{/if}
|
|
313
|
+
</div>
|
|
314
|
+
{/if}
|
|
315
|
+
|
|
316
|
+
<style>
|
|
317
|
+
.policy-field {
|
|
318
|
+
display: flex;
|
|
319
|
+
flex-direction: column;
|
|
320
|
+
gap: var(--smrt-spacing-1, 0.25rem);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.policy-field__label {
|
|
324
|
+
font: var(--smrt-typography-label-large-font, inherit);
|
|
325
|
+
color: var(--smrt-color-on-surface, currentColor);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.policy-field__required {
|
|
329
|
+
color: var(--smrt-color-error, #b3261e);
|
|
330
|
+
margin-left: 0.25em;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.policy-field__hint {
|
|
334
|
+
font: var(--smrt-typography-body-small-font, inherit);
|
|
335
|
+
color: var(--smrt-color-on-surface-variant, currentColor);
|
|
336
|
+
margin: 0;
|
|
337
|
+
}
|
|
338
|
+
</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PolicyField — layout-neutral wrapper around any input.
|
|
3
|
+
*
|
|
4
|
+
* Contributes:
|
|
5
|
+
* - Visibility by resolved tier × current mode (advanced fields hidden in
|
|
6
|
+
* basic mode)
|
|
7
|
+
* - Initial-value prefill from the resolved default (new records only —
|
|
8
|
+
* never clobbers loaded values)
|
|
9
|
+
* - Label rendering (the resolved label; when the policy has no label the
|
|
10
|
+
* label element is omitted rather than fabricated from the raw field name,
|
|
11
|
+
* and tooltip-density help falls back to a visible hint)
|
|
12
|
+
* - Help hint rendered from resolved help (manifest description → org/user
|
|
13
|
+
* override)
|
|
14
|
+
* - Required marker
|
|
15
|
+
*
|
|
16
|
+
* Label ↔ control association: the default wrapper renders
|
|
17
|
+
* `<label for={name}>`, so the wrapped control should carry an `id` equal to
|
|
18
|
+
* the field `name`. When the first wrapped control has a different `id`, the
|
|
19
|
+
* `for` attribute is re-pointed to it after mount; when it has no `id` at
|
|
20
|
+
* all, the wrapper assigns `id={name}` so the association is guaranteed.
|
|
21
|
+
*
|
|
22
|
+
* Headless escape hatch: pass a `render` snippet receiving
|
|
23
|
+
* `{visible, label, help, defaultValue, required, tier}` for fully custom
|
|
24
|
+
* rendering. Without a snippet, PolicyField wraps children in a label + help
|
|
25
|
+
* affordance and controls visibility.
|
|
26
|
+
*
|
|
27
|
+
* Graceful degradation: outside a FieldPolicyProvider, PolicyField renders its
|
|
28
|
+
* children verbatim (no visibility filtering, no label/help injection) so
|
|
29
|
+
* forms can adopt it incrementally.
|
|
30
|
+
*/
|
|
31
|
+
import type { Snippet } from 'svelte';
|
|
32
|
+
import type { PolicyFieldSnippetProps } from '../types.js';
|
|
33
|
+
export interface Props {
|
|
34
|
+
/** The field name matching a key in the resolved policy's `fields`. */
|
|
35
|
+
name: string;
|
|
36
|
+
/** Stable per-form control id; defaults to `name` for standalone use. */
|
|
37
|
+
id?: string;
|
|
38
|
+
/** Stable per-form help id; defaults to `${id}-help`. */
|
|
39
|
+
helpId?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Children — the actual input(s). Always rendered when the field is visible
|
|
42
|
+
* (unless the `render` snippet escape hatch is used).
|
|
43
|
+
*/
|
|
44
|
+
children?: Snippet;
|
|
45
|
+
/**
|
|
46
|
+
* Headless escape hatch: a snippet receiving all resolved policy data for
|
|
47
|
+
* fully custom rendering. When provided, it replaces the default label +
|
|
48
|
+
* children + help layout entirely.
|
|
49
|
+
*/
|
|
50
|
+
render?: Snippet<[PolicyFieldSnippetProps]>;
|
|
51
|
+
/**
|
|
52
|
+
* Whether this is a new record. When true (default) and the field has a
|
|
53
|
+
* resolved default, the provider can prefill. Set to false for loaded
|
|
54
|
+
* records to prevent overwriting existing values.
|
|
55
|
+
* @default true
|
|
56
|
+
*/
|
|
57
|
+
isNewRecord?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Override the resolved label. When provided, takes precedence over the
|
|
60
|
+
* policy label.
|
|
61
|
+
*/
|
|
62
|
+
label?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Override the resolved help text. When provided, takes precedence over
|
|
65
|
+
* the policy help.
|
|
66
|
+
*/
|
|
67
|
+
help?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Density of the help hint: 'hint' (inline hint text, default) or
|
|
70
|
+
* 'tooltip' (title attribute on the label).
|
|
71
|
+
* @default 'hint'
|
|
72
|
+
*/
|
|
73
|
+
helpDensity?: 'hint' | 'tooltip';
|
|
74
|
+
/** Additional class for the wrapper element. */
|
|
75
|
+
class?: string;
|
|
76
|
+
}
|
|
77
|
+
declare const PolicyField: import("svelte").Component<Props, {}, "">;
|
|
78
|
+
type PolicyField = ReturnType<typeof PolicyField>;
|
|
79
|
+
export default PolicyField;
|
|
80
|
+
//# sourceMappingURL=PolicyField.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PolicyField.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/PolicyField.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAG3D,MAAM,WAAW,KAAK;IACpB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC5C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8OD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte context for the field-policy form primitives (#2048).
|
|
3
|
+
*
|
|
4
|
+
* `FieldPolicyProvider` seeds a {@link FieldPolicyContextValue} into the
|
|
5
|
+
* component tree from a pre-resolved {@link ResolvedObjectFieldPolicy} (SSR
|
|
6
|
+
* `initialData` or a client fetch by the app — no smrt-web import in the
|
|
7
|
+
* primitives). `PolicyField` and the mode-UX compositions read it through
|
|
8
|
+
* {@link getFieldPolicyContext} / {@link tryGetFieldPolicyContext}.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the established `tryGet…` pattern (`tryGetControlInteractionContext`,
|
|
11
|
+
* `useI18n`'s process-wide fallback) so `PolicyField` degrades gracefully
|
|
12
|
+
* outside a Provider: an unwrapped field renders its children verbatim, and a
|
|
13
|
+
* consumer can adopt the primitives incrementally without wrapping the whole
|
|
14
|
+
* form.
|
|
15
|
+
*/
|
|
16
|
+
import type { ResolvedFieldPolicy, ResolvedObjectFieldPolicy } from '../types.js';
|
|
17
|
+
/** UI display mode for progressive disclosure. */
|
|
18
|
+
export type FieldPolicyMode = 'basic' | 'advanced';
|
|
19
|
+
/**
|
|
20
|
+
* Reactive mode store implemented as a class with `$state` fields. Svelte 5
|
|
21
|
+
* tracks `$state` on class instances correctly across context boundaries —
|
|
22
|
+
* the instance is the reactive root, not the closure.
|
|
23
|
+
*/
|
|
24
|
+
export declare class FieldPolicyModeStore {
|
|
25
|
+
current: FieldPolicyMode;
|
|
26
|
+
constructor(initial?: FieldPolicyMode);
|
|
27
|
+
set(next: FieldPolicyMode): void;
|
|
28
|
+
toggle(): void;
|
|
29
|
+
}
|
|
30
|
+
/** Svelte 5 runes-backed reactive context published by FieldPolicyProvider. */
|
|
31
|
+
export interface FieldPolicyContextValue {
|
|
32
|
+
/** The fully resolved policy for one object. */
|
|
33
|
+
readonly policy: ResolvedObjectFieldPolicy;
|
|
34
|
+
/** Reactive mode store (basic/advanced) owned by the provider. */
|
|
35
|
+
readonly mode: FieldPolicyModeStore;
|
|
36
|
+
/**
|
|
37
|
+
* Lookup of a single field's resolved policy by name.
|
|
38
|
+
* Returns `undefined` when the field is not in the resolved set.
|
|
39
|
+
*/
|
|
40
|
+
getField(fieldName: string): ResolvedFieldPolicy | undefined;
|
|
41
|
+
/** Count of advanced-visible fields for the disclosure label. */
|
|
42
|
+
readonly advancedFieldCount: number;
|
|
43
|
+
}
|
|
44
|
+
/** Publish the context (called by FieldPolicyProvider). */
|
|
45
|
+
export declare function setFieldPolicyContext(context: FieldPolicyContextValue): void;
|
|
46
|
+
/** Read the context, throwing when there is no Provider. */
|
|
47
|
+
export declare function getFieldPolicyContext(): FieldPolicyContextValue;
|
|
48
|
+
/**
|
|
49
|
+
* Optional reader — returns `undefined` when no Provider wraps the consumer.
|
|
50
|
+
* Used by `PolicyField` so it can render children verbatim outside a Provider
|
|
51
|
+
* (graceful degradation / incremental adoption).
|
|
52
|
+
*/
|
|
53
|
+
export declare function tryGetFieldPolicyContext(): FieldPolicyContextValue | undefined;
|
|
54
|
+
//# sourceMappingURL=context.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/context.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,aAAa,CAAC;AAErB,kDAAkD;AAClD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,UAAU,CAAC;AAEnD;;;;GAIG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,kBAAoC;gBAE/B,OAAO,GAAE,eAAyB;IAI9C,GAAG,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI;IAIhC,MAAM,IAAI,IAAI;CAGf;AAED,+EAA+E;AAC/E,MAAM,WAAW,uBAAuB;IACtC,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAC;IAC7D,iEAAiE;IACjE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAID,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAE5E;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,IAAI,uBAAuB,CAS/D;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IACpC,uBAAuB,GACvB,SAAS,CAIZ"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte context for the field-policy form primitives (#2048).
|
|
3
|
+
*
|
|
4
|
+
* `FieldPolicyProvider` seeds a {@link FieldPolicyContextValue} into the
|
|
5
|
+
* component tree from a pre-resolved {@link ResolvedObjectFieldPolicy} (SSR
|
|
6
|
+
* `initialData` or a client fetch by the app — no smrt-web import in the
|
|
7
|
+
* primitives). `PolicyField` and the mode-UX compositions read it through
|
|
8
|
+
* {@link getFieldPolicyContext} / {@link tryGetFieldPolicyContext}.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the established `tryGet…` pattern (`tryGetControlInteractionContext`,
|
|
11
|
+
* `useI18n`'s process-wide fallback) so `PolicyField` degrades gracefully
|
|
12
|
+
* outside a Provider: an unwrapped field renders its children verbatim, and a
|
|
13
|
+
* consumer can adopt the primitives incrementally without wrapping the whole
|
|
14
|
+
* form.
|
|
15
|
+
*/
|
|
16
|
+
import { getContext, setContext } from 'svelte';
|
|
17
|
+
/**
|
|
18
|
+
* Reactive mode store implemented as a class with `$state` fields. Svelte 5
|
|
19
|
+
* tracks `$state` on class instances correctly across context boundaries —
|
|
20
|
+
* the instance is the reactive root, not the closure.
|
|
21
|
+
*/
|
|
22
|
+
export class FieldPolicyModeStore {
|
|
23
|
+
current = $state('basic');
|
|
24
|
+
constructor(initial = 'basic') {
|
|
25
|
+
this.current = initial;
|
|
26
|
+
}
|
|
27
|
+
set(next) {
|
|
28
|
+
this.current = next;
|
|
29
|
+
}
|
|
30
|
+
toggle() {
|
|
31
|
+
this.current = this.current === 'basic' ? 'advanced' : 'basic';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const FIELD_POLICY_CONTEXT_KEY = Symbol('smrt-field-policy-context');
|
|
35
|
+
/** Publish the context (called by FieldPolicyProvider). */
|
|
36
|
+
export function setFieldPolicyContext(context) {
|
|
37
|
+
setContext(FIELD_POLICY_CONTEXT_KEY, context);
|
|
38
|
+
}
|
|
39
|
+
/** Read the context, throwing when there is no Provider. */
|
|
40
|
+
export function getFieldPolicyContext() {
|
|
41
|
+
const context = tryGetFieldPolicyContext();
|
|
42
|
+
if (!context) {
|
|
43
|
+
throw new Error('FieldPolicy context not found. Wrap your form in ' +
|
|
44
|
+
'<FieldPolicyProvider> from @happyvertical/smrt-fields/svelte.');
|
|
45
|
+
}
|
|
46
|
+
return context;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Optional reader — returns `undefined` when no Provider wraps the consumer.
|
|
50
|
+
* Used by `PolicyField` so it can render children verbatim outside a Provider
|
|
51
|
+
* (graceful degradation / incremental adoption).
|
|
52
|
+
*/
|
|
53
|
+
export function tryGetFieldPolicyContext() {
|
|
54
|
+
return getContext(FIELD_POLICY_CONTEXT_KEY);
|
|
55
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Pure adapter between resolved field policy and smrt-ui DataTable columns. */
|
|
2
|
+
import type { ResolvedObjectFieldPolicy } from '../types.js';
|
|
3
|
+
export interface PolicyDataTableColumn {
|
|
4
|
+
id: string;
|
|
5
|
+
/** DataTable still enforces this independently; excluding it here is useful
|
|
6
|
+
* to callers that inspect the returned set before rendering. */
|
|
7
|
+
hidden?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Produce DataTable's `visibleColumnIds` set from a resolved policy.
|
|
11
|
+
*
|
|
12
|
+
* Only policy-mapped hidden fields are removed. Columns with no matching field
|
|
13
|
+
* remain visible so computed, selection, and action columns keep working.
|
|
14
|
+
* Static `column.hidden` remains authoritative and can never be undone.
|
|
15
|
+
*/
|
|
16
|
+
export declare function policyToVisibleColumnIds(policy: ResolvedObjectFieldPolicy, columns: readonly PolicyDataTableColumn[], fieldNameByColumnId?: Readonly<Record<string, string>>): Set<string>;
|
|
17
|
+
//# sourceMappingURL=data-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../src/svelte/data-table.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX;oEACgE;IAChE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,yBAAyB,EACjC,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,mBAAmB,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GACzD,GAAG,CAAC,MAAM,CAAC,CASb"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Produce DataTable's `visibleColumnIds` set from a resolved policy.
|
|
3
|
+
*
|
|
4
|
+
* Only policy-mapped hidden fields are removed. Columns with no matching field
|
|
5
|
+
* remain visible so computed, selection, and action columns keep working.
|
|
6
|
+
* Static `column.hidden` remains authoritative and can never be undone.
|
|
7
|
+
*/
|
|
8
|
+
export function policyToVisibleColumnIds(policy, columns, fieldNameByColumnId = {}) {
|
|
9
|
+
const visible = new Set();
|
|
10
|
+
for (const column of columns) {
|
|
11
|
+
if (column.hidden)
|
|
12
|
+
continue;
|
|
13
|
+
const fieldName = fieldNameByColumnId[column.id] ?? column.id;
|
|
14
|
+
const field = policy.fields[fieldName];
|
|
15
|
+
if (field?.visibility !== 'hidden')
|
|
16
|
+
visible.add(column.id);
|
|
17
|
+
}
|
|
18
|
+
return visible;
|
|
19
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-neutral client contracts and pure helpers for the policy gear.
|
|
3
|
+
*
|
|
4
|
+
* Hosts normally adapt these methods to their generated FieldPolicyCollection
|
|
5
|
+
* client. Identity is intentionally absent from mutations: the server derives
|
|
6
|
+
* tenant/user scope from the authenticated ambient context.
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldPolicyEditorRow, FieldPolicyEditorState, FieldPolicyEditorStateResult, FieldPolicyScopeType } from '../types.js';
|
|
9
|
+
export interface FieldPolicyEditorAdapter {
|
|
10
|
+
load(input: {
|
|
11
|
+
objectRef: string;
|
|
12
|
+
}): Promise<FieldPolicyEditorStateResult>;
|
|
13
|
+
create(input: FieldPolicyEditorMutation): Promise<unknown>;
|
|
14
|
+
update(input: FieldPolicyEditorMutation & {
|
|
15
|
+
id: string;
|
|
16
|
+
}): Promise<unknown>;
|
|
17
|
+
delete(input: {
|
|
18
|
+
id: string;
|
|
19
|
+
}): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
/** Generated-client compatible sparse write shape. `defaultValue` is JSON. */
|
|
22
|
+
export interface FieldPolicyEditorMutation {
|
|
23
|
+
objectRef: string;
|
|
24
|
+
fieldName: string;
|
|
25
|
+
scopeType: FieldPolicyScopeType;
|
|
26
|
+
defaultValue: string | null;
|
|
27
|
+
displayOrder: number | null;
|
|
28
|
+
help: string | null;
|
|
29
|
+
label: string | null;
|
|
30
|
+
locked: boolean | null;
|
|
31
|
+
visibility: 'basic' | 'advanced' | 'hidden' | null;
|
|
32
|
+
}
|
|
33
|
+
export type FieldPolicyEditorTab = 'organization' | 'personal';
|
|
34
|
+
export type FieldPolicyOrganizationScope = 'app' | 'tenant';
|
|
35
|
+
export interface FieldPolicyEditorDraft {
|
|
36
|
+
defaultEnabled: boolean;
|
|
37
|
+
defaultValue: unknown;
|
|
38
|
+
displayOrder: number | null;
|
|
39
|
+
help: string | null;
|
|
40
|
+
label: string | null;
|
|
41
|
+
locked: boolean | null;
|
|
42
|
+
visibility: 'basic' | 'advanced' | 'hidden' | null;
|
|
43
|
+
}
|
|
44
|
+
export declare function isFieldPolicyEditorState(result: unknown, expectedObjectRef?: string): result is FieldPolicyEditorState;
|
|
45
|
+
/** Safe message extraction for generated custom actions, whose client type is `any`. */
|
|
46
|
+
export declare function editorStateErrorMessage(result: unknown): string;
|
|
47
|
+
export declare function rowForScope(state: FieldPolicyEditorState, fieldName: string, scope: FieldPolicyScopeType): FieldPolicyEditorRow | undefined;
|
|
48
|
+
export declare function draftFromRow(row?: FieldPolicyEditorRow): FieldPolicyEditorDraft;
|
|
49
|
+
export declare function mutationFromDraft(objectRef: string, fieldName: string, scopeType: FieldPolicyScopeType, draft: FieldPolicyEditorDraft): FieldPolicyEditorMutation;
|
|
50
|
+
/**
|
|
51
|
+
* Serialize the explicit default-value wire channel without ever leaking an
|
|
52
|
+
* `undefined` payload into a generated client. JSON null remains intentional.
|
|
53
|
+
*/
|
|
54
|
+
export declare function serializeDefaultValue(value: unknown): string;
|
|
55
|
+
export declare function defaultValueSerializationError(value: unknown): string | null;
|
|
56
|
+
export declare function requiredVisibilityIsInvalid(required: boolean, visibility: FieldPolicyEditorDraft['visibility'], effectiveDefault: unknown): boolean;
|
|
57
|
+
export declare function hasUsableDefault(value: unknown): boolean;
|
|
58
|
+
/** Structural AdminShell seam; importing smrt-svelte here would invert the DAG. */
|
|
59
|
+
export interface FieldPolicyFocusTool {
|
|
60
|
+
id: string;
|
|
61
|
+
label: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
order?: number;
|
|
64
|
+
component?: unknown;
|
|
65
|
+
render?: unknown;
|
|
66
|
+
subject?: {
|
|
67
|
+
type: string;
|
|
68
|
+
id: string;
|
|
69
|
+
label?: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export interface FieldPolicyFocusToolRegistrar {
|
|
73
|
+
registerFocusTool(tool: FieldPolicyFocusTool): () => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Registers an object-form settings tool with AdminShell-compatible state.
|
|
77
|
+
* AdminShell passes `{ tool, shell }` to a component focus tool; callers pass
|
|
78
|
+
* that component through `tool.component` without Fields importing the shell.
|
|
79
|
+
*/
|
|
80
|
+
export declare function registerFieldPolicyFocusTool(shell: FieldPolicyFocusToolRegistrar, objectRef: string, tool: Omit<FieldPolicyFocusTool, 'subject'>): () => void;
|
|
81
|
+
//# sourceMappingURL=field-policy-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-policy-editor.d.ts","sourceRoot":"","sources":["../../src/svelte/field-policy-editor.ts"],"names":[],"mappings":"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"}
|