@vit-foundation/ui 0.25.0 → 0.26.0

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 (62) hide show
  1. package/README.md +1 -1
  2. package/dist/community.d.ts +12 -3
  3. package/dist/community.js +7 -3
  4. package/dist/components/pages/ProjectPage.svelte +3 -9
  5. package/dist/components/pages/ProjectPage.svelte.d.ts +3 -5
  6. package/dist/components/pages/WeeklyPage.svelte +7 -12
  7. package/dist/components/pages/WeeklyPage.svelte.d.ts +7 -9
  8. package/dist/components/ui/Modal.svelte +32 -11
  9. package/dist/components/ui/Modal.svelte.d.ts +17 -1
  10. package/dist/config/context.d.ts +0 -6
  11. package/dist/config/context.js +1 -1
  12. package/dist/config/index.d.ts +2 -2
  13. package/dist/config/index.js +1 -1
  14. package/dist/content-components.d.ts +6 -3
  15. package/dist/content-components.js +5 -1
  16. package/dist/contract.d.ts +8 -4
  17. package/dist/contract.js +7 -3
  18. package/dist/edit/Editable.svelte +17 -161
  19. package/dist/edit/Editable.svelte.d.ts +2 -50
  20. package/dist/edit/chrome/AddSlot.svelte +11 -78
  21. package/dist/edit/chrome/AddSlot.svelte.d.ts +2 -15
  22. package/dist/edit/chrome/EditFrame.svelte +15 -158
  23. package/dist/edit/chrome/EditFrame.svelte.d.ts +2 -31
  24. package/dist/edit/chrome/EditPanel.svelte +14 -22
  25. package/dist/edit/chrome/EditPanel.svelte.d.ts +2 -13
  26. package/dist/edit/chrome/LinkEdit.svelte +13 -277
  27. package/dist/edit/chrome/LinkEdit.svelte.d.ts +2 -48
  28. package/dist/edit/chrome-props.d.ts +96 -0
  29. package/dist/edit/chrome-props.js +1 -0
  30. package/dist/edit/context.d.ts +38 -5
  31. package/dist/edit/context.js +18 -7
  32. package/dist/edit/index.d.ts +6 -4
  33. package/dist/edit/index.js +4 -3
  34. package/dist/edit/live/AddSlot.svelte +83 -0
  35. package/dist/edit/live/AddSlot.svelte.d.ts +4 -0
  36. package/dist/edit/live/EditFrame.svelte +153 -0
  37. package/dist/edit/live/EditFrame.svelte.d.ts +4 -0
  38. package/dist/edit/live/EditPanel.svelte +25 -0
  39. package/dist/edit/live/EditPanel.svelte.d.ts +4 -0
  40. package/dist/edit/live/Editable.svelte +162 -0
  41. package/dist/edit/live/Editable.svelte.d.ts +4 -0
  42. package/dist/edit/live/LinkEdit.svelte +276 -0
  43. package/dist/edit/live/LinkEdit.svelte.d.ts +4 -0
  44. package/dist/edit/{chrome → live}/PropertyRow.svelte +1 -1
  45. package/dist/edit/{commit.svelte.d.ts → live/commit.svelte.d.ts} +2 -2
  46. package/dist/edit/live/index.d.ts +10 -0
  47. package/dist/edit/live/index.js +14 -0
  48. package/package.json +1 -1
  49. package/dist/components/account/index.d.ts +0 -3
  50. package/dist/components/account/index.js +0 -2
  51. package/dist/components/auth/index.d.ts +0 -7
  52. package/dist/components/auth/index.js +0 -4
  53. package/dist/components/contact/index.d.ts +0 -2
  54. package/dist/components/contact/index.js +0 -1
  55. package/dist/content/index.d.ts +0 -6
  56. package/dist/content/index.js +0 -3
  57. /package/dist/edit/{chrome → live}/ConfirmDialog.svelte +0 -0
  58. /package/dist/edit/{chrome → live}/ConfirmDialog.svelte.d.ts +0 -0
  59. /package/dist/edit/{chrome → live}/EditPopover.svelte +0 -0
  60. /package/dist/edit/{chrome → live}/EditPopover.svelte.d.ts +0 -0
  61. /package/dist/edit/{chrome → live}/PropertyRow.svelte.d.ts +0 -0
  62. /package/dist/edit/{commit.svelte.js → live/commit.svelte.js} +0 -0
@@ -1,89 +1,22 @@
1
1
  <script lang="ts">
2
- import Icon from '../../components/ui/Icon.svelte';
3
- import { getUiConfig } from '../../config/context.js';
4
- import { getEditAdapter } from '../context.js';
5
- import type { EntityOp } from '../types.js';
2
+ import type { AddSlotProps } from '../chrome-props.js';
3
+ import { getEditAdapter, getEditChrome } from '../context.js';
6
4
 
7
5
  /**
8
- * The "+" affordance of an editable collection. Renders ONLY while the
9
- * adapter is editing AND implements `applyOp` — otherwise nothing, the
10
- * byte-identical invariant. The op resolves server-side and the host's
11
- * refresh brings the new row in; this control only shows pending/error.
6
+ * The "+" affordance's GATE. Renders ONLY while the adapter is editing
7
+ * AND implements `applyOp` — otherwise nothing, the byte-identical
8
+ * invariant. The control itself, its pending state and its announcement
9
+ * live in `edit/live/AddSlot.svelte`, installed by the host.
12
10
  */
13
- interface Props {
14
- op: Extract<EntityOp, { kind: 'create' }>;
15
- /** The entity noun for the label, e.g. "una fita". */
16
- label?: string;
17
- }
18
-
19
- let { op, label = '' }: Props = $props();
11
+ let { op, label = '' }: AddSlotProps = $props();
20
12
 
21
13
  const adapter = getEditAdapter();
22
- const config = getUiConfig();
23
-
24
- let pending = $state(false);
25
- let announcement = $state('');
14
+ const chrome = getEditChrome();
26
15
 
27
16
  const active = $derived((adapter?.isEditing ?? false) && adapter?.applyOp !== undefined);
28
- const text = $derived(config.editMessages.edit_add({ label }).trim());
29
-
30
- async function add(): Promise<void> {
31
- if (!adapter?.applyOp || pending) return;
32
- pending = true;
33
- announcement = '';
34
- try {
35
- await adapter.applyOp(op);
36
- } catch {
37
- announcement = config.editMessages.edit_addFailed();
38
- } finally {
39
- pending = false;
40
- }
41
- }
42
17
  </script>
43
18
 
44
- {#if active}
45
- <button type="button" class="add" disabled={pending} onclick={() => void add()}>
46
- <Icon name="plus" size={16} />
47
- {text}
48
- </button>
49
- <span class="status" role="status">{announcement}</span>
19
+ {#if active && chrome}
20
+ {@const Live = chrome.AddSlot}
21
+ <Live {op} {label} />
50
22
  {/if}
51
-
52
- <style>
53
- .add {
54
- display: inline-flex;
55
- align-items: center;
56
- gap: var(--space-1);
57
- padding: var(--space-1) var(--space-3);
58
- border: 1px dashed var(--color-ink-muted);
59
- border-radius: 999px;
60
- background: transparent;
61
- color: var(--color-ink);
62
- font: inherit;
63
- font-size: var(--text-sm);
64
- cursor: pointer;
65
- transition: border-color var(--transition-fast);
66
- }
67
-
68
- .add:hover:not(:disabled) {
69
- border-color: var(--color-brand);
70
- color: var(--color-brand);
71
- }
72
-
73
- .add:disabled {
74
- opacity: 0.5;
75
- cursor: default;
76
- }
77
-
78
- .status {
79
- position: absolute;
80
- width: 1px;
81
- height: 1px;
82
- padding: 0;
83
- margin: -1px;
84
- overflow: hidden;
85
- clip-path: inset(50%);
86
- white-space: nowrap;
87
- border: 0;
88
- }
89
- </style>
@@ -1,17 +1,4 @@
1
- import type { EntityOp } from '../types.js';
2
- /**
3
- * The "+" affordance of an editable collection. Renders ONLY while the
4
- * adapter is editing AND implements `applyOp` — otherwise nothing, the
5
- * byte-identical invariant. The op resolves server-side and the host's
6
- * refresh brings the new row in; this control only shows pending/error.
7
- */
8
- interface Props {
9
- op: Extract<EntityOp, {
10
- kind: 'create';
11
- }>;
12
- /** The entity noun for the label, e.g. "una fita". */
13
- label?: string;
14
- }
15
- declare const AddSlot: import("svelte").Component<Props, {}, "">;
1
+ import type { AddSlotProps } from '../chrome-props.js';
2
+ declare const AddSlot: import("svelte").Component<AddSlotProps, {}, "">;
16
3
  type AddSlot = ReturnType<typeof AddSlot>;
17
4
  export default AddSlot;
@@ -1,171 +1,28 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
- import IconButton from '../../components/ui/IconButton.svelte';
4
- import { getUiConfig } from '../../config/context.js';
5
- import { getEditAdapter } from '../context.js';
6
- import type { EntityOp } from '../types.js';
7
- import ConfirmDialog from './ConfirmDialog.svelte';
8
- import EditPopover from './EditPopover.svelte';
9
-
10
- /** What one framed component tells the frame about itself. */
11
- export interface EditFrameSpec {
12
- /** Human name of the thing, e.g. "Fita: Neix la fundació". */
13
- label: string;
14
- /** Gates the gear — true when the panel snippet has rows to show. */
15
- hasPanel?: boolean;
16
- /** Gates the trash: removing this item from its collection. */
17
- removeOp?: Extract<EntityOp, { kind: 'remove' }>;
18
- }
2
+ import type { EditFrameProps } from '../chrome-props.js';
3
+ import { getEditAdapter, getEditChrome } from '../context.js';
19
4
 
20
5
  /**
21
- * The page-builder wrapper: a corner toolbar (revealed on hover AND
22
- * :focus-within — never hover-only) with a gear that opens the property
23
- * panel and a trash that confirms, then applies the remove op.
24
- *
25
- * Triple-gated per affordance: spec present ∧ adapter editing ∧ the
26
- * capability method present. With nothing to offer it renders the children
27
- * ALONE — zero wrapper element, so an unadapted app's DOM is byte-identical
28
- * (pinned by test). Components mount it INSIDE their own root element, so
29
- * the wrapper never disturbs the parent's flex/grid.
6
+ * The page-builder wrapper's GATE. With no spec, no adapter or editing
7
+ * off it renders the children ALONE — zero wrapper element, so an
8
+ * unadapted app's DOM is byte-identical (pinned by test). Editing, it
9
+ * mounts the live frame the host installed (`edit/live/EditFrame.svelte`),
10
+ * which owns the corner toolbar, the property popover and the confirmed
11
+ * remove, and which applies the finer per-affordance gates itself.
12
+ * Components mount this INSIDE their own root element, so the wrapper
13
+ * never disturbs the parent's flex/grid.
30
14
  */
31
- interface Props {
32
- spec?: EditFrameSpec;
33
- /** The panel content — an EditPanel with this component's rows. */
34
- panel?: Snippet;
35
- children: Snippet;
36
- }
37
-
38
- let { spec, panel, children }: Props = $props();
15
+ let { spec, panel, children }: EditFrameProps = $props();
39
16
 
40
17
  const adapter = getEditAdapter();
41
- const config = getUiConfig();
18
+ const chrome = getEditChrome();
42
19
 
43
20
  const editing = $derived(spec !== undefined && (adapter?.isEditing ?? false));
44
- const showGear = $derived(
45
- editing && spec?.hasPanel === true && panel !== undefined && adapter?.saveProperty !== undefined
46
- );
47
- const showTrash = $derived(
48
- editing && spec?.removeOp !== undefined && adapter?.applyOp !== undefined
49
- );
50
- const framed = $derived(showGear || showTrash);
51
-
52
- let panelOpen = $state(false);
53
- let confirming = $state(false);
54
- let removing = $state(false);
55
- let announcement = $state('');
56
- let toolbar: HTMLDivElement | undefined = $state();
57
-
58
- function closePanel(): void {
59
- panelOpen = false;
60
- // The popover owned focus; hand it back to the control that opened it.
61
- toolbar?.querySelector('button')?.focus();
62
- }
63
-
64
- async function remove(): Promise<void> {
65
- if (!adapter?.applyOp || !spec?.removeOp) return;
66
- removing = true;
67
- try {
68
- await adapter.applyOp(spec.removeOp);
69
- // The host's refresh unmounts this frame with the removed item.
70
- confirming = false;
71
- } catch {
72
- confirming = false;
73
- announcement = config.editMessages.edit_saveError();
74
- } finally {
75
- removing = false;
76
- }
77
- }
78
21
  </script>
79
22
 
80
- {#if framed && spec}
81
- <div class="vit-edit-frame">
82
- {@render children()}
83
- <div class="toolbar" bind:this={toolbar}>
84
- {#if showGear}
85
- <IconButton
86
- icon="gear"
87
- label={config.editMessages.edit_properties({ label: spec.label })}
88
- onclick={() => (panelOpen = !panelOpen)}
89
- />
90
- {/if}
91
- {#if showTrash}
92
- <IconButton
93
- icon="trash"
94
- label={config.editMessages.edit_remove()}
95
- onclick={() => (confirming = true)}
96
- />
97
- {/if}
98
- </div>
99
- {#if panelOpen && panel}
100
- <EditPopover
101
- label={config.editMessages.edit_properties({ label: spec.label })}
102
- onclose={closePanel}
103
- >
104
- {@render panel()}
105
- </EditPopover>
106
- {/if}
107
- {#if showTrash}
108
- <ConfirmDialog
109
- open={confirming}
110
- title={config.editMessages.edit_remove()}
111
- message={config.editMessages.edit_removeConfirm({ label: spec.label })}
112
- confirmLabel={config.editMessages.edit_remove()}
113
- pending={removing}
114
- onconfirm={() => void remove()}
115
- oncancel={() => (confirming = false)}
116
- />
117
- {/if}
118
- <span class="status" role="status">{announcement}</span>
119
- </div>
23
+ {#if editing && chrome}
24
+ {@const Live = chrome.EditFrame}
25
+ <Live {spec} {panel} {children} />
120
26
  {:else}
121
27
  {@render children()}
122
28
  {/if}
123
-
124
- <style>
125
- .vit-edit-frame {
126
- position: relative;
127
- border-radius: var(--radius);
128
- outline: 1px dashed transparent;
129
- outline-offset: 4px;
130
- transition: outline-color var(--transition-fast);
131
- }
132
-
133
- .vit-edit-frame:hover,
134
- .vit-edit-frame:focus-within {
135
- outline-color: var(--color-brand);
136
- }
137
-
138
- .toolbar {
139
- position: absolute;
140
- top: calc(-1 * var(--space-2));
141
- right: 0;
142
- display: flex;
143
- gap: 2px;
144
- z-index: var(--z-raised);
145
- background: var(--color-surface);
146
- border: 1px solid var(--color-hairline);
147
- border-radius: var(--radius);
148
- box-shadow: var(--shadow-1);
149
- opacity: 0;
150
- pointer-events: none;
151
- transition: opacity var(--transition-fast);
152
- }
153
-
154
- .vit-edit-frame:hover .toolbar,
155
- .vit-edit-frame:focus-within .toolbar {
156
- opacity: 1;
157
- pointer-events: auto;
158
- }
159
-
160
- .status {
161
- position: absolute;
162
- width: 1px;
163
- height: 1px;
164
- padding: 0;
165
- margin: -1px;
166
- overflow: hidden;
167
- clip-path: inset(50%);
168
- white-space: nowrap;
169
- border: 0;
170
- }
171
- </style>
@@ -1,33 +1,4 @@
1
- import type { Snippet } from 'svelte';
2
- import type { EntityOp } from '../types.js';
3
- /** What one framed component tells the frame about itself. */
4
- export interface EditFrameSpec {
5
- /** Human name of the thing, e.g. "Fita: Neix la fundació". */
6
- label: string;
7
- /** Gates the gear — true when the panel snippet has rows to show. */
8
- hasPanel?: boolean;
9
- /** Gates the trash: removing this item from its collection. */
10
- removeOp?: Extract<EntityOp, {
11
- kind: 'remove';
12
- }>;
13
- }
14
- /**
15
- * The page-builder wrapper: a corner toolbar (revealed on hover AND
16
- * :focus-within — never hover-only) with a gear that opens the property
17
- * panel and a trash that confirms, then applies the remove op.
18
- *
19
- * Triple-gated per affordance: spec present ∧ adapter editing ∧ the
20
- * capability method present. With nothing to offer it renders the children
21
- * ALONE — zero wrapper element, so an unadapted app's DOM is byte-identical
22
- * (pinned by test). Components mount it INSIDE their own root element, so
23
- * the wrapper never disturbs the parent's flex/grid.
24
- */
25
- interface Props {
26
- spec?: EditFrameSpec;
27
- /** The panel content — an EditPanel with this component's rows. */
28
- panel?: Snippet;
29
- children: Snippet;
30
- }
31
- declare const EditFrame: import("svelte").Component<Props, {}, "">;
1
+ import type { EditFrameProps } from '../chrome-props.js';
2
+ declare const EditFrame: import("svelte").Component<EditFrameProps, {}, "">;
32
3
  type EditFrame = ReturnType<typeof EditFrame>;
33
4
  export default EditFrame;
@@ -1,29 +1,21 @@
1
1
  <script lang="ts">
2
- import type { PropertyDescriptor, PropertyValue } from '../types.js';
3
- import PropertyRow from './PropertyRow.svelte';
2
+ import type { EditPanelProps } from '../chrome-props.js';
3
+ import { getEditChrome } from '../context.js';
4
4
 
5
5
  /**
6
- * The property form an EditFrame's popover holds: one PropertyRow per
7
- * entry, each with its own independent commit lifecycle. The component
8
- * knows nothing about the entity — the descriptors say everything.
6
+ * The property form's GATE. A renderer builds its panel snippet from this
7
+ * whether or not anyone will ever open it, so this is the name it imports;
8
+ * the rows themselves (`edit/live/EditPanel.svelte`, one PropertyRow per
9
+ * entry with its own commit lifecycle) exist only where a host installed
10
+ * the chrome. The snippet is only rendered inside a live frame's popover,
11
+ * so with no chrome there is nothing to render and nothing is.
9
12
  */
10
- interface Props {
11
- rows: { descriptor: PropertyDescriptor; value: PropertyValue }[];
12
- }
13
+ let { rows }: EditPanelProps = $props();
13
14
 
14
- let { rows }: Props = $props();
15
+ const chrome = getEditChrome();
15
16
  </script>
16
17
 
17
- <div class="panel">
18
- {#each rows as row (JSON.stringify(row.descriptor.ref) + row.descriptor.label)}
19
- <PropertyRow descriptor={row.descriptor} value={row.value} />
20
- {/each}
21
- </div>
22
-
23
- <style>
24
- .panel {
25
- display: flex;
26
- flex-direction: column;
27
- gap: var(--space-3);
28
- }
29
- </style>
18
+ {#if chrome}
19
+ {@const Live = chrome.EditPanel}
20
+ <Live {rows} />
21
+ {/if}
@@ -1,15 +1,4 @@
1
- import type { PropertyDescriptor, PropertyValue } from '../types.js';
2
- /**
3
- * The property form an EditFrame's popover holds: one PropertyRow per
4
- * entry, each with its own independent commit lifecycle. The component
5
- * knows nothing about the entity — the descriptors say everything.
6
- */
7
- interface Props {
8
- rows: {
9
- descriptor: PropertyDescriptor;
10
- value: PropertyValue;
11
- }[];
12
- }
13
- declare const EditPanel: import("svelte").Component<Props, {}, "">;
1
+ import type { EditPanelProps } from '../chrome-props.js';
2
+ declare const EditPanel: import("svelte").Component<EditPanelProps, {}, "">;
14
3
  type EditPanel = ReturnType<typeof EditPanel>;
15
4
  export default EditPanel;