@happyvertical/smrt-fields 0.40.60 → 0.40.61
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 +90 -2
- 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/collections/FieldPolicyCollection.d.ts +95 -0
- package/dist/collections/FieldPolicyCollection.d.ts.map +1 -0
- package/dist/field-definitions.d.ts +77 -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 +9 -532
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +752 -55
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +45 -3
- package/dist/models/FieldPolicy.d.ts +163 -0
- package/dist/models/FieldPolicy.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 +50 -7
- package/dist/svelte/__tests__/FieldPolicyControlPanel.test.js +223 -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 +556 -0
- package/dist/svelte/__tests__/ObjectFormGeneratedApi.integration.test.js +436 -0
- package/dist/svelte/__tests__/PolicyField.test.js +373 -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 +20 -0
- package/dist/svelte/__tests__/fixtures/ObjectFormActionsFixture.svelte.d.ts +11 -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/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 +261 -0
- package/dist/svelte/components/FieldPolicyControlPanel.svelte.d.ts +26 -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 +17 -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 +135 -0
- package/dist/svelte/components/FieldPolicyGearProvider.svelte.d.ts +17 -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/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 +351 -0
- package/dist/svelte/components/ObjectForm.svelte.d.ts +40 -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 +14 -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 +58 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +38 -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/types.d.ts +101 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +4 -0
- package/dist/types.d.ts +276 -203
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +33 -8
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/** Accessible, transport-neutral editor for context-derived policy rows. */
|
|
3
|
+
|
|
4
|
+
import { Modal } from '@happyvertical/smrt-ui/feedback';
|
|
5
|
+
import { Input, Select, Toggle } from '@happyvertical/smrt-ui/forms';
|
|
6
|
+
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
7
|
+
import { type Component, untrack } from 'svelte';
|
|
8
|
+
import type {
|
|
9
|
+
FieldPolicyEditorState,
|
|
10
|
+
FieldPolicyScopeType,
|
|
11
|
+
} from '../../types.js';
|
|
12
|
+
import {
|
|
13
|
+
defaultValueSerializationError,
|
|
14
|
+
draftFromRow,
|
|
15
|
+
type FieldPolicyEditorAdapter,
|
|
16
|
+
type FieldPolicyEditorDraft,
|
|
17
|
+
type FieldPolicyEditorTab,
|
|
18
|
+
type FieldPolicyOrganizationScope,
|
|
19
|
+
mutationFromDraft,
|
|
20
|
+
requiredVisibilityIsInvalid,
|
|
21
|
+
rowForScope,
|
|
22
|
+
} from '../field-policy-editor.js';
|
|
23
|
+
import type {
|
|
24
|
+
FieldInputComponent,
|
|
25
|
+
FieldInputRegistry,
|
|
26
|
+
} from '../input-registry.js';
|
|
27
|
+
import type { FieldInputProps, ObjectFormFieldDefinition } from '../types.js';
|
|
28
|
+
import FieldInput from './FieldInput.svelte';
|
|
29
|
+
|
|
30
|
+
interface Props {
|
|
31
|
+
state: FieldPolicyEditorState;
|
|
32
|
+
adapter: FieldPolicyEditorAdapter;
|
|
33
|
+
fields: Readonly<Record<string, ObjectFormFieldDefinition>>;
|
|
34
|
+
inputRegistry?: FieldInputRegistry;
|
|
35
|
+
organizationScope?: FieldPolicyOrganizationScope;
|
|
36
|
+
onclose: () => void;
|
|
37
|
+
onmutated: () => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let {
|
|
41
|
+
state: editorState,
|
|
42
|
+
adapter,
|
|
43
|
+
fields,
|
|
44
|
+
inputRegistry,
|
|
45
|
+
organizationScope = 'tenant',
|
|
46
|
+
onclose,
|
|
47
|
+
onmutated,
|
|
48
|
+
}: Props = $props();
|
|
49
|
+
|
|
50
|
+
let tab = $state<FieldPolicyEditorTab>(
|
|
51
|
+
untrack(() =>
|
|
52
|
+
editorState.capabilities.manage ? 'organization' : 'personal',
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
let drafts = $state<Record<string, FieldPolicyEditorDraft>>({});
|
|
56
|
+
let saving = $state<string | null>(null);
|
|
57
|
+
let error = $state<string | null>(null);
|
|
58
|
+
let invalidDefaults = $state<Set<string>>(new Set());
|
|
59
|
+
const tabInstanceId = $props.id();
|
|
60
|
+
|
|
61
|
+
const scope = $derived<FieldPolicyScopeType>(
|
|
62
|
+
tab === 'organization' ? organizationScope : 'user',
|
|
63
|
+
);
|
|
64
|
+
const availableTabs = $derived<FieldPolicyEditorTab[]>([
|
|
65
|
+
...(editorState.capabilities.manage ? (['organization'] as const) : []),
|
|
66
|
+
...(editorState.capabilities.personalize ? (['personal'] as const) : []),
|
|
67
|
+
]);
|
|
68
|
+
const orderedFields = $derived(
|
|
69
|
+
Object.values(editorState.policy.fields).sort(
|
|
70
|
+
(left, right) =>
|
|
71
|
+
(left.order ?? Number.MAX_SAFE_INTEGER) -
|
|
72
|
+
(right.order ?? Number.MAX_SAFE_INTEGER) ||
|
|
73
|
+
left.fieldName.localeCompare(right.fieldName),
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
function tabId(tabName: FieldPolicyEditorTab): string {
|
|
78
|
+
return `field-policy-tab-${tabInstanceId}-${tabName}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function tabPanelId(tabName: FieldPolicyEditorTab): string {
|
|
82
|
+
return `field-policy-panel-${tabInstanceId}-${tabName}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function selectTab(nextTab: FieldPolicyEditorTab, focus = false): void {
|
|
86
|
+
tab = nextTab;
|
|
87
|
+
if (focus) {
|
|
88
|
+
// The selected button receives tabindex=0 after this reactive update.
|
|
89
|
+
requestAnimationFrame(() => {
|
|
90
|
+
document.getElementById(tabId(nextTab))?.focus();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function handleTabKeydown(
|
|
96
|
+
event: KeyboardEvent,
|
|
97
|
+
currentTab: FieldPolicyEditorTab,
|
|
98
|
+
): void {
|
|
99
|
+
const currentIndex = availableTabs.indexOf(currentTab);
|
|
100
|
+
if (currentIndex === -1 || availableTabs.length < 2) return;
|
|
101
|
+
|
|
102
|
+
let nextIndex: number | null = null;
|
|
103
|
+
switch (event.key) {
|
|
104
|
+
case 'ArrowRight':
|
|
105
|
+
nextIndex = (currentIndex + 1) % availableTabs.length;
|
|
106
|
+
break;
|
|
107
|
+
case 'ArrowLeft':
|
|
108
|
+
nextIndex =
|
|
109
|
+
(currentIndex - 1 + availableTabs.length) % availableTabs.length;
|
|
110
|
+
break;
|
|
111
|
+
case 'Home':
|
|
112
|
+
nextIndex = 0;
|
|
113
|
+
break;
|
|
114
|
+
case 'End':
|
|
115
|
+
nextIndex = availableTabs.length - 1;
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
const nextTab = availableTabs[nextIndex];
|
|
123
|
+
if (nextTab) selectTab(nextTab, true);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function draftFor(fieldName: string): FieldPolicyEditorDraft {
|
|
127
|
+
const key = `${scope}:${fieldName}`;
|
|
128
|
+
return (
|
|
129
|
+
drafts[key] ?? draftFromRow(rowForScope(editorState, fieldName, scope))
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function setDraft(
|
|
134
|
+
fieldName: string,
|
|
135
|
+
patch: Partial<FieldPolicyEditorDraft>,
|
|
136
|
+
): void {
|
|
137
|
+
const key = `${scope}:${fieldName}`;
|
|
138
|
+
drafts[key] = { ...draftFor(fieldName), ...patch };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function defaultValueFor(fieldName: string): unknown {
|
|
142
|
+
const draft = draftFor(fieldName);
|
|
143
|
+
if (draft.defaultEnabled) return draft.defaultValue;
|
|
144
|
+
const row = rowForScope(editorState, fieldName, scope);
|
|
145
|
+
// Clearing a current row must not count its own resolved default. Replay
|
|
146
|
+
// only lower layers, preserving explicit null as a present (unusable) default.
|
|
147
|
+
if (row?.defaultValue != null) {
|
|
148
|
+
// Personal callers intentionally receive no org rows/layers. The server
|
|
149
|
+
// supplies only whether the hidden code/app/tenant fallback is usable,
|
|
150
|
+
// which is enough to enforce the required-field invariant without
|
|
151
|
+
// disclosing any lower-layer default value.
|
|
152
|
+
if (scope === 'user') {
|
|
153
|
+
return editorState.personalLowerDefaultUsable[fieldName]
|
|
154
|
+
? true
|
|
155
|
+
: undefined;
|
|
156
|
+
}
|
|
157
|
+
const definition = fields[fieldName];
|
|
158
|
+
let fallback: unknown =
|
|
159
|
+
definition && Object.hasOwn(definition, 'default')
|
|
160
|
+
? definition.default
|
|
161
|
+
: undefined;
|
|
162
|
+
for (const layer of editorState.policy.layers[fieldName] ?? []) {
|
|
163
|
+
if (scope === 'app' && layer.layer !== 'code') continue;
|
|
164
|
+
if (scope === 'tenant' && layer.layer === 'user') continue;
|
|
165
|
+
if (
|
|
166
|
+
scope === 'tenant' &&
|
|
167
|
+
layer.layer === 'tenant' &&
|
|
168
|
+
layer.tenantId === row.tenantId
|
|
169
|
+
)
|
|
170
|
+
continue;
|
|
171
|
+
if (layer.delta.default) fallback = layer.delta.default.value;
|
|
172
|
+
}
|
|
173
|
+
return fallback;
|
|
174
|
+
}
|
|
175
|
+
return editorState.policy.fields[fieldName].defaultValue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function userLocked(fieldName: string): boolean {
|
|
179
|
+
return scope === 'user' && editorState.policy.fields[fieldName].locked;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function invalidRequiredDemotion(fieldName: string): boolean {
|
|
183
|
+
const field = editorState.policy.fields[fieldName];
|
|
184
|
+
const draft = draftFor(fieldName);
|
|
185
|
+
return requiredVisibilityIsInvalid(
|
|
186
|
+
field.required,
|
|
187
|
+
draft.visibility,
|
|
188
|
+
defaultValueFor(fieldName),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function defaultSerializationError(fieldName: string): string | null {
|
|
193
|
+
const draft = draftFor(fieldName);
|
|
194
|
+
if (draft.defaultEnabled && invalidDefaults.has(`${scope}:${fieldName}`)) {
|
|
195
|
+
return 'Enter valid JSON before saving this default.';
|
|
196
|
+
}
|
|
197
|
+
return draft.defaultEnabled
|
|
198
|
+
? defaultValueSerializationError(draft.defaultValue)
|
|
199
|
+
: null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function setDefaultValidity(
|
|
203
|
+
fieldScope: FieldPolicyScopeType,
|
|
204
|
+
fieldName: string,
|
|
205
|
+
valid: boolean,
|
|
206
|
+
): void {
|
|
207
|
+
const next = new Set(invalidDefaults);
|
|
208
|
+
const key = `${fieldScope}:${fieldName}`;
|
|
209
|
+
if (valid) next.delete(key);
|
|
210
|
+
else next.add(key);
|
|
211
|
+
invalidDefaults = next;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function inputFor(fieldName: string): FieldInputComponent {
|
|
215
|
+
const definition = fields[fieldName];
|
|
216
|
+
return (
|
|
217
|
+
inputRegistry?.resolve(
|
|
218
|
+
editorState.policy.objectRef,
|
|
219
|
+
fieldName,
|
|
220
|
+
definition?.type ?? 'text',
|
|
221
|
+
) ?? (FieldInput as unknown as Component<FieldInputProps>)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function editorField(fieldName: string) {
|
|
226
|
+
const definition = fields[fieldName] ?? { type: 'text' };
|
|
227
|
+
const policy = editorState.policy.fields[fieldName];
|
|
228
|
+
return {
|
|
229
|
+
name: fieldName,
|
|
230
|
+
definition,
|
|
231
|
+
group: policy.group,
|
|
232
|
+
order: policy.order,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function save(fieldName: string): Promise<void> {
|
|
237
|
+
if (
|
|
238
|
+
userLocked(fieldName) ||
|
|
239
|
+
invalidRequiredDemotion(fieldName) ||
|
|
240
|
+
defaultSerializationError(fieldName)
|
|
241
|
+
) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
saving = fieldName;
|
|
245
|
+
error = null;
|
|
246
|
+
try {
|
|
247
|
+
const row = rowForScope(editorState, fieldName, scope);
|
|
248
|
+
const mutation = mutationFromDraft(
|
|
249
|
+
editorState.policy.objectRef,
|
|
250
|
+
fieldName,
|
|
251
|
+
scope,
|
|
252
|
+
draftFor(fieldName),
|
|
253
|
+
);
|
|
254
|
+
if (row) await adapter.update({ ...mutation, id: row.id });
|
|
255
|
+
else await adapter.create(mutation);
|
|
256
|
+
drafts = {};
|
|
257
|
+
await onmutated();
|
|
258
|
+
} catch (cause) {
|
|
259
|
+
error =
|
|
260
|
+
cause instanceof Error ? cause.message : 'Unable to save field settings.';
|
|
261
|
+
} finally {
|
|
262
|
+
saving = null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function reset(fieldName: string): Promise<void> {
|
|
267
|
+
const row = rowForScope(editorState, fieldName, scope);
|
|
268
|
+
if (!row || userLocked(fieldName)) return;
|
|
269
|
+
saving = fieldName;
|
|
270
|
+
error = null;
|
|
271
|
+
try {
|
|
272
|
+
await adapter.delete({ id: row.id });
|
|
273
|
+
drafts = {};
|
|
274
|
+
await onmutated();
|
|
275
|
+
} catch (cause) {
|
|
276
|
+
error =
|
|
277
|
+
cause instanceof Error
|
|
278
|
+
? cause.message
|
|
279
|
+
: 'Unable to reset field settings.';
|
|
280
|
+
} finally {
|
|
281
|
+
saving = null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
</script>
|
|
285
|
+
|
|
286
|
+
<Modal open={true} onClose={onclose} title="Field settings" size="xl" ariaDescribedBy="field-policy-editor-description">
|
|
287
|
+
<div class="field-policy-editor">
|
|
288
|
+
<p id="field-policy-editor-description">Overrides are sparse. Reset removes this scope’s row and inherits the effective policy.</p>
|
|
289
|
+
|
|
290
|
+
<div class="field-policy-editor__tabs" role="tablist" aria-label="Field policy scope">
|
|
291
|
+
{#if editorState.capabilities.manage}
|
|
292
|
+
<Button
|
|
293
|
+
type="button"
|
|
294
|
+
role="tab"
|
|
295
|
+
id={tabId('organization')}
|
|
296
|
+
aria-selected={tab === 'organization'}
|
|
297
|
+
aria-controls={tabPanelId('organization')}
|
|
298
|
+
tabindex={tab === 'organization' ? 0 : -1}
|
|
299
|
+
onclick={() => selectTab('organization')}
|
|
300
|
+
onkeydown={(event: KeyboardEvent) => handleTabKeydown(event, 'organization')}
|
|
301
|
+
>
|
|
302
|
+
Organization
|
|
303
|
+
</Button>
|
|
304
|
+
{/if}
|
|
305
|
+
{#if editorState.capabilities.personalize}
|
|
306
|
+
<Button
|
|
307
|
+
type="button"
|
|
308
|
+
role="tab"
|
|
309
|
+
id={tabId('personal')}
|
|
310
|
+
aria-selected={tab === 'personal'}
|
|
311
|
+
aria-controls={tabPanelId('personal')}
|
|
312
|
+
tabindex={tab === 'personal' ? 0 : -1}
|
|
313
|
+
onclick={() => selectTab('personal')}
|
|
314
|
+
onkeydown={(event: KeyboardEvent) => handleTabKeydown(event, 'personal')}
|
|
315
|
+
>
|
|
316
|
+
Just me
|
|
317
|
+
</Button>
|
|
318
|
+
{/if}
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
{#if error}
|
|
322
|
+
<p class="field-policy-editor__error" role="alert">{error}</p>
|
|
323
|
+
{/if}
|
|
324
|
+
|
|
325
|
+
<div
|
|
326
|
+
role="tabpanel"
|
|
327
|
+
id={tabPanelId(tab)}
|
|
328
|
+
aria-labelledby={tabId(tab)}
|
|
329
|
+
aria-label={tab === 'organization' ? 'Organization settings' : 'Personal settings'}
|
|
330
|
+
>
|
|
331
|
+
{#each orderedFields as policyField (policyField.fieldName)}
|
|
332
|
+
{@const fieldName = policyField.fieldName}
|
|
333
|
+
{@const defaultScope = scope}
|
|
334
|
+
{@const draft = draftFor(fieldName)}
|
|
335
|
+
{@const InputComponent = inputFor(fieldName)}
|
|
336
|
+
<fieldset class="field-policy-editor__field" disabled={userLocked(fieldName) || saving === fieldName}>
|
|
337
|
+
<legend>{policyField.label ?? fieldName}</legend>
|
|
338
|
+
{#if userLocked(fieldName)}
|
|
339
|
+
<p class="field-policy-editor__notice">This field is locked by your organization and cannot be personalized.</p>
|
|
340
|
+
{/if}
|
|
341
|
+
{#if policyField.required}
|
|
342
|
+
<p id={`${fieldName}-required-invariant`} class="field-policy-editor__notice">
|
|
343
|
+
Required fields without a usable default must remain Basic.
|
|
344
|
+
</p>
|
|
345
|
+
{/if}
|
|
346
|
+
<label>
|
|
347
|
+
Visibility
|
|
348
|
+
<Select
|
|
349
|
+
value={draft.visibility ?? ''}
|
|
350
|
+
aria-describedby={policyField.required ? `${fieldName}-required-invariant` : undefined}
|
|
351
|
+
onchange={(event: Event) => setDraft(fieldName, { visibility: ((event.currentTarget as HTMLSelectElement).value || null) as FieldPolicyEditorDraft['visibility'] })}
|
|
352
|
+
>
|
|
353
|
+
<option value="">Inherit</option>
|
|
354
|
+
<option value="basic">Basic</option>
|
|
355
|
+
<option value="advanced">Advanced</option>
|
|
356
|
+
<option value="hidden">Hidden</option>
|
|
357
|
+
</Select>
|
|
358
|
+
</label>
|
|
359
|
+
{#if invalidRequiredDemotion(fieldName)}
|
|
360
|
+
<p class="field-policy-editor__error" role="status">Add a usable default before making this required field advanced or hidden.</p>
|
|
361
|
+
{/if}
|
|
362
|
+
<Toggle
|
|
363
|
+
checked={draft.defaultEnabled}
|
|
364
|
+
label="Override default value"
|
|
365
|
+
onchange={(value) => {
|
|
366
|
+
setDraft(fieldName, { defaultEnabled: value });
|
|
367
|
+
if (!value) setDefaultValidity(defaultScope, fieldName, true);
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
370
|
+
{#if draft.defaultEnabled}
|
|
371
|
+
<label for={`${fieldName}-default`}>Default value</label>
|
|
372
|
+
{#key `${scope}:${fieldName}`}
|
|
373
|
+
<InputComponent
|
|
374
|
+
id={`${fieldName}-default`}
|
|
375
|
+
name={`${fieldName}-default`}
|
|
376
|
+
label={`Default ${policyField.label ?? fieldName}`}
|
|
377
|
+
field={editorField(fieldName)}
|
|
378
|
+
value={draft.defaultValue}
|
|
379
|
+
required={false}
|
|
380
|
+
disabled={false}
|
|
381
|
+
onvaluechange={(value) => setDraft(fieldName, { defaultValue: value })}
|
|
382
|
+
onvaliditychange={(valid) => setDefaultValidity(defaultScope, fieldName, valid)}
|
|
383
|
+
/>
|
|
384
|
+
{/key}
|
|
385
|
+
{#if defaultSerializationError(fieldName)}
|
|
386
|
+
<p class="field-policy-editor__error" role="status">
|
|
387
|
+
{defaultSerializationError(fieldName)}
|
|
388
|
+
</p>
|
|
389
|
+
{/if}
|
|
390
|
+
{/if}
|
|
391
|
+
<label>Label <Input value={draft.label ?? ''} oninput={(event: Event) => setDraft(fieldName, { label: (event.currentTarget as HTMLInputElement).value })} /></label>
|
|
392
|
+
<label>Help <Input value={draft.help ?? ''} oninput={(event: Event) => setDraft(fieldName, { help: (event.currentTarget as HTMLInputElement).value })} /></label>
|
|
393
|
+
<label>Display order <Input type="number" value={draft.displayOrder ?? ''} oninput={(event: Event) => { const value = (event.currentTarget as HTMLInputElement).value; setDraft(fieldName, { displayOrder: value === '' ? null : Number(value) }); }} /></label>
|
|
394
|
+
{#if scope !== 'user'}
|
|
395
|
+
<Toggle checked={draft.locked === true} ariaLabel={`Lock ${policyField.label ?? fieldName}`} onchange={(value) => setDraft(fieldName, { locked: value ? true : null })} />
|
|
396
|
+
<span>Lock personal overrides</span>
|
|
397
|
+
{/if}
|
|
398
|
+
<div class="field-policy-editor__actions">
|
|
399
|
+
<Button type="button" onclick={() => save(fieldName)} disabled={invalidRequiredDemotion(fieldName) || !!defaultSerializationError(fieldName)}>{saving === fieldName ? 'Saving…' : 'Save'}</Button>
|
|
400
|
+
{#if rowForScope(editorState, fieldName, scope)}
|
|
401
|
+
<Button type="button" variant="ghost" onclick={() => reset(fieldName)}>Reset to inherited</Button>
|
|
402
|
+
{/if}
|
|
403
|
+
</div>
|
|
404
|
+
</fieldset>
|
|
405
|
+
{/each}
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
</Modal>
|
|
409
|
+
|
|
410
|
+
<style>
|
|
411
|
+
.field-policy-editor { background: var(--smrt-color-surface, white); color: var(--smrt-color-on-surface, #1f2937); display: grid; gap: var(--smrt-spacing-4, 1rem); max-block-size: min(90vh, 56rem); overflow: auto; padding: var(--smrt-spacing-5, 1.25rem); }
|
|
412
|
+
.field-policy-editor__actions, .field-policy-editor__tabs { align-items: center; display: flex; gap: var(--smrt-spacing-2, .5rem); justify-content: space-between; }
|
|
413
|
+
.field-policy-editor__field { border: 1px solid var(--smrt-color-outline, #d0d5dd); display: grid; gap: var(--smrt-spacing-3, .75rem); margin: 0 0 var(--smrt-spacing-3, .75rem); padding: var(--smrt-spacing-3, .75rem); }
|
|
414
|
+
.field-policy-editor__field label { display: grid; gap: var(--smrt-spacing-1, .25rem); }
|
|
415
|
+
.field-policy-editor__error { color: var(--smrt-color-error, #b42318); }
|
|
416
|
+
.field-policy-editor__notice { margin: 0; }
|
|
417
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Component } from 'svelte';
|
|
2
|
+
import type { FieldPolicyEditorState } from '../../types.js';
|
|
3
|
+
import { type FieldPolicyEditorAdapter, type FieldPolicyOrganizationScope } from '../field-policy-editor.js';
|
|
4
|
+
import type { FieldInputRegistry } from '../input-registry.js';
|
|
5
|
+
import type { ObjectFormFieldDefinition } from '../types.js';
|
|
6
|
+
interface Props {
|
|
7
|
+
state: FieldPolicyEditorState;
|
|
8
|
+
adapter: FieldPolicyEditorAdapter;
|
|
9
|
+
fields: Readonly<Record<string, ObjectFormFieldDefinition>>;
|
|
10
|
+
inputRegistry?: FieldInputRegistry;
|
|
11
|
+
organizationScope?: FieldPolicyOrganizationScope;
|
|
12
|
+
onclose: () => void;
|
|
13
|
+
onmutated: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
declare const FieldPolicyEditor: Component<Props, {}, "">;
|
|
16
|
+
type FieldPolicyEditor = ReturnType<typeof FieldPolicyEditor>;
|
|
17
|
+
export default FieldPolicyEditor;
|
|
18
|
+
//# sourceMappingURL=FieldPolicyEditor.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldPolicyEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/FieldPolicyEditor.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,QAAQ,CAAC;AACjD,OAAO,KAAK,EACV,sBAAsB,EAEvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAGL,KAAK,wBAAwB,EAG7B,KAAK,4BAA4B,EAIlC,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAmB,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAI9E,UAAU,KAAK;IACb,KAAK,EAAE,sBAAsB,CAAC;IAC9B,OAAO,EAAE,wBAAwB,CAAC;IAClC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAC5D,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AA4VD,QAAA,MAAM,iBAAiB,0BAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
3
|
+
import { getFieldPolicyGearContext } from '../gear-context.svelte.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
label = 'Field settings',
|
|
7
|
+
class: className = '',
|
|
8
|
+
}: { label?: string; class?: string } = $props();
|
|
9
|
+
const gear = getFieldPolicyGearContext();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{#if gear.canEdit}
|
|
13
|
+
<Button type="button" variant="ghost" class={className} aria-haspopup="dialog" onclick={() => gear.show()}>
|
|
14
|
+
<span aria-hidden="true">⚙</span>
|
|
15
|
+
<span>{label}</span>
|
|
16
|
+
</Button>
|
|
17
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
label?: string;
|
|
3
|
+
class?: string;
|
|
4
|
+
};
|
|
5
|
+
declare const FieldPolicyGearButton: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type FieldPolicyGearButton = ReturnType<typeof FieldPolicyGearButton>;
|
|
7
|
+
export default FieldPolicyGearButton;
|
|
8
|
+
//# sourceMappingURL=FieldPolicyGearButton.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldPolicyGearButton.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/FieldPolicyGearButton.svelte.ts"],"names":[],"mappings":"AAMC,KAAK,gBAAgB,GAAI;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAqB7D,QAAA,MAAM,qBAAqB,sDAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { FieldPolicyEditorState } from '../../types.js';
|
|
4
|
+
import {
|
|
5
|
+
editorStateErrorMessage,
|
|
6
|
+
type FieldPolicyEditorAdapter,
|
|
7
|
+
type FieldPolicyOrganizationScope,
|
|
8
|
+
isFieldPolicyEditorState,
|
|
9
|
+
} from '../field-policy-editor.js';
|
|
10
|
+
import {
|
|
11
|
+
type FieldPolicyGearController,
|
|
12
|
+
setFieldPolicyGearContext,
|
|
13
|
+
} from '../gear-context.svelte.js';
|
|
14
|
+
import type { FieldInputRegistry } from '../input-registry.js';
|
|
15
|
+
import type { ObjectFormFieldDefinition } from '../types.js';
|
|
16
|
+
import FieldPolicyEditor from './FieldPolicyEditor.svelte';
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
objectRef: string;
|
|
20
|
+
fields: Readonly<Record<string, ObjectFormFieldDefinition>>;
|
|
21
|
+
adapter: FieldPolicyEditorAdapter;
|
|
22
|
+
inputRegistry?: FieldInputRegistry;
|
|
23
|
+
/** Hosts in an app-wide context pass app; tenant hosts retain the default. */
|
|
24
|
+
organizationScope?: FieldPolicyOrganizationScope;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
objectRef,
|
|
30
|
+
fields,
|
|
31
|
+
adapter,
|
|
32
|
+
inputRegistry,
|
|
33
|
+
organizationScope = 'tenant',
|
|
34
|
+
children,
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
let editorState = $state<FieldPolicyEditorState | null>(null);
|
|
38
|
+
let error = $state<string | null>(null);
|
|
39
|
+
let open = $state(false);
|
|
40
|
+
let loading = $state(false);
|
|
41
|
+
|
|
42
|
+
const controller: FieldPolicyGearController = {
|
|
43
|
+
get state() {
|
|
44
|
+
return editorState;
|
|
45
|
+
},
|
|
46
|
+
get error() {
|
|
47
|
+
return error;
|
|
48
|
+
},
|
|
49
|
+
get open() {
|
|
50
|
+
return open;
|
|
51
|
+
},
|
|
52
|
+
get canEdit() {
|
|
53
|
+
return (
|
|
54
|
+
!!editorState &&
|
|
55
|
+
(editorState.capabilities.manage || editorState.capabilities.personalize)
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
show() {
|
|
59
|
+
if (this.canEdit) open = true;
|
|
60
|
+
},
|
|
61
|
+
hide() {
|
|
62
|
+
open = false;
|
|
63
|
+
},
|
|
64
|
+
reload,
|
|
65
|
+
};
|
|
66
|
+
setFieldPolicyGearContext(controller);
|
|
67
|
+
|
|
68
|
+
function isPermissionDenied(value: unknown): boolean {
|
|
69
|
+
return (
|
|
70
|
+
!!value &&
|
|
71
|
+
typeof value === 'object' &&
|
|
72
|
+
((value as { status?: unknown }).status === 403 ||
|
|
73
|
+
(value as { code?: unknown }).code === 'permission_denied' ||
|
|
74
|
+
(value as { response?: { status?: unknown } }).response?.status === 403)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function reload(generation = ++loadGeneration): Promise<void> {
|
|
79
|
+
loading = true;
|
|
80
|
+
error = null;
|
|
81
|
+
try {
|
|
82
|
+
const result = await adapter.load({ objectRef });
|
|
83
|
+
if (generation !== loadGeneration) return;
|
|
84
|
+
if (isFieldPolicyEditorState(result, objectRef)) {
|
|
85
|
+
editorState = result;
|
|
86
|
+
} else {
|
|
87
|
+
editorState = null;
|
|
88
|
+
error = isPermissionDenied(result)
|
|
89
|
+
? null
|
|
90
|
+
: editorStateErrorMessage(result);
|
|
91
|
+
open = false;
|
|
92
|
+
}
|
|
93
|
+
} catch (cause) {
|
|
94
|
+
if (generation !== loadGeneration) return;
|
|
95
|
+
editorState = null;
|
|
96
|
+
error = isPermissionDenied(cause)
|
|
97
|
+
? null
|
|
98
|
+
: cause instanceof Error
|
|
99
|
+
? cause.message
|
|
100
|
+
: 'Unable to load field settings.';
|
|
101
|
+
open = false;
|
|
102
|
+
} finally {
|
|
103
|
+
if (generation === loadGeneration) loading = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let loadGeneration = 0;
|
|
108
|
+
$effect(() => {
|
|
109
|
+
const generation = ++loadGeneration;
|
|
110
|
+
editorState = null;
|
|
111
|
+
error = null;
|
|
112
|
+
open = false;
|
|
113
|
+
void reload(generation);
|
|
114
|
+
return () => {
|
|
115
|
+
// A delayed transport response must never repopulate a prior object.
|
|
116
|
+
if (loadGeneration === generation) loadGeneration++;
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
{@render children?.()}
|
|
122
|
+
{#if error && !open}
|
|
123
|
+
<p class="field-policy-gear__error" role="alert">{error}</p>
|
|
124
|
+
{/if}
|
|
125
|
+
{#if open && editorState}
|
|
126
|
+
<FieldPolicyEditor state={editorState} {adapter} {fields} {inputRegistry} {organizationScope} onclose={() => controller.hide()} onmutated={reload} />
|
|
127
|
+
{/if}
|
|
128
|
+
{#if loading}
|
|
129
|
+
<span class="field-policy-gear__loading" aria-live="polite">Loading field settings…</span>
|
|
130
|
+
{/if}
|
|
131
|
+
|
|
132
|
+
<style>
|
|
133
|
+
.field-policy-gear__error { color: var(--smrt-color-error, #b42318); }
|
|
134
|
+
.field-policy-gear__loading { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
135
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type FieldPolicyEditorAdapter, type FieldPolicyOrganizationScope } from '../field-policy-editor.js';
|
|
3
|
+
import type { FieldInputRegistry } from '../input-registry.js';
|
|
4
|
+
import type { ObjectFormFieldDefinition } from '../types.js';
|
|
5
|
+
interface Props {
|
|
6
|
+
objectRef: string;
|
|
7
|
+
fields: Readonly<Record<string, ObjectFormFieldDefinition>>;
|
|
8
|
+
adapter: FieldPolicyEditorAdapter;
|
|
9
|
+
inputRegistry?: FieldInputRegistry;
|
|
10
|
+
/** Hosts in an app-wide context pass app; tenant hosts retain the default. */
|
|
11
|
+
organizationScope?: FieldPolicyOrganizationScope;
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
}
|
|
14
|
+
declare const FieldPolicyGearProvider: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type FieldPolicyGearProvider = ReturnType<typeof FieldPolicyGearProvider>;
|
|
16
|
+
export default FieldPolicyGearProvider;
|
|
17
|
+
//# sourceMappingURL=FieldPolicyGearProvider.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldPolicyGearProvider.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/FieldPolicyGearProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,4BAA4B,EAElC,MAAM,2BAA2B,CAAC;AAKnC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAI7D,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAC5D,OAAO,EAAE,wBAAwB,CAAC;IAClC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAuHD,QAAA,MAAM,uBAAuB,2CAAwC,CAAC;AACtE,KAAK,uBAAuB,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAC1E,eAAe,uBAAuB,CAAC"}
|