rimecms 1.0.0 → 1.1.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 (174) hide show
  1. package/dist/adapter-sqlite/generate-schema/templates.server.d.ts +9 -9
  2. package/dist/adapter-sqlite/generate-schema/templates.server.js +9 -9
  3. package/dist/adapter-sqlite/paths.server.js +24 -15
  4. package/dist/adapter-sqlite/rows.server.d.ts +1 -1
  5. package/dist/core/adapter.d.ts +8 -4
  6. package/dist/core/boot.server.d.ts +2 -2
  7. package/dist/core/config/context.server.d.ts +2 -2
  8. package/dist/core/dev/codegen/routes/common.server.d.ts +2 -0
  9. package/dist/core/dev/codegen/routes/common.server.js +7 -3
  10. package/dist/core/dev/codegen/routes/util.server.d.ts +2 -1
  11. package/dist/core/dev/codegen/routes/util.server.js +4 -1
  12. package/dist/core/dev/config-memo.server.js +4 -0
  13. package/dist/core/i18n/en/fields.d.ts +1 -1
  14. package/dist/core/i18n/en/fields.js +1 -1
  15. package/dist/core/i18n/fr/fields.d.ts +1 -1
  16. package/dist/core/i18n/fr/fields.js +1 -1
  17. package/dist/core/prototype/collection/operations/duplicate.js +1 -1
  18. package/dist/core/prototype/collection/thumbnail/hooks/set-document-thumbnail.server.js +1 -1
  19. package/dist/core/prototype/shared/title/hooks/set-document-title.server.js +1 -1
  20. package/dist/core/prototype/shared/url/addresses.server.d.ts +10 -0
  21. package/dist/core/prototype/shared/url/addresses.server.js +17 -17
  22. package/dist/core/prototype/shared/url/hooks/detach-paths.server.d.ts +3 -2
  23. package/dist/core/prototype/shared/url/hooks/detach-paths.server.js +9 -8
  24. package/dist/core/prototype/shared/url/hooks/sync-paths.server.d.ts +3 -2
  25. package/dist/core/prototype/shared/url/hooks/sync-paths.server.js +5 -4
  26. package/dist/fields/blocks/component/AddBlockButton.svelte +1 -1
  27. package/dist/fields/blocks/component/Block.svelte +4 -5
  28. package/dist/fields/blocks/component/Blocks.svelte +17 -13
  29. package/dist/fields/blocks/component/focus/BlocksFocus.svelte +53 -65
  30. package/dist/fields/blocks/component/focus/DefaultRender.svelte +108 -0
  31. package/dist/fields/blocks/component/focus/{RenderPlaceholder.svelte.d.ts → DefaultRender.svelte.d.ts} +3 -3
  32. package/dist/fields/blocks/component/focus/Inspector.svelte +160 -0
  33. package/dist/fields/blocks/component/focus/Inspector.svelte.d.ts +7 -0
  34. package/dist/fields/blocks/component/focus/Layers.svelte +16 -3
  35. package/dist/fields/blocks/component/focus/LayersList.svelte +28 -11
  36. package/dist/fields/blocks/component/focus/LayersList.svelte.d.ts +2 -0
  37. package/dist/fields/blocks/component/focus/Palette.svelte +3 -3
  38. package/dist/fields/blocks/component/focus/Renders.svelte +66 -15
  39. package/dist/fields/blocks/component/focus/Renders.svelte.d.ts +5 -1
  40. package/dist/fields/blocks/component/focus/StagePlaceholder.svelte +1 -4
  41. package/dist/fields/blocks/component/focus/focus.svelte.d.ts +26 -12
  42. package/dist/fields/blocks/component/focus/focus.svelte.js +114 -57
  43. package/dist/fields/blocks/component/picker/BlockTile.svelte +3 -5
  44. package/dist/fields/combobox/component/ComboBox.svelte +1 -1
  45. package/dist/fields/combobox/component/combobox.css +1 -1
  46. package/dist/fields/date/component/Date.svelte +1 -1
  47. package/dist/fields/group/component/Group.svelte +4 -4
  48. package/dist/fields/link/component/Link.svelte +1 -1
  49. package/dist/fields/link/component/RessourceInput.svelte +2 -2
  50. package/dist/fields/link/index.d.ts +1 -1
  51. package/dist/fields/number/component/Number.svelte +1 -1
  52. package/dist/fields/relation/component/Cell.svelte +3 -1
  53. package/dist/fields/relation/component/RelationInline.svelte +51 -20
  54. package/dist/fields/relation/component/default/Default.svelte +3 -3
  55. package/dist/fields/relation/component/upload/Browse.svelte +2 -2
  56. package/dist/fields/relation/component/upload/Upload.svelte +2 -2
  57. package/dist/fields/relation/index.d.ts +1 -41
  58. package/dist/fields/relation/index.js +0 -67
  59. package/dist/fields/relation/util.d.ts +69 -0
  60. package/dist/fields/relation/util.js +103 -0
  61. package/dist/fields/rich-text/client.d.ts +2 -5
  62. package/dist/fields/rich-text/client.js +1 -3
  63. package/dist/fields/rich-text/component/Cell.svelte +1 -1
  64. package/dist/fields/rich-text/component/bubble-menu/bubble-menu.css +1 -1
  65. package/dist/fields/rich-text/component/bubble-menu/node-selector/node-selector.css +1 -1
  66. package/dist/fields/rich-text/core/features/link/component/link-selector.svelte +2 -2
  67. package/dist/fields/rich-text/core/features/resource/resource-extension.js +3 -3
  68. package/dist/fields/rich-text/core/features/resource/resource.svelte +2 -2
  69. package/dist/fields/rich-text/core/features/upload/upload-extension.js +3 -4
  70. package/dist/fields/rich-text/core/features/upload/upload.svelte +2 -2
  71. package/dist/fields/rich-text/index.d.ts +0 -17
  72. package/dist/fields/rich-text/index.js +0 -44
  73. package/dist/fields/rich-text/json.d.ts +18 -0
  74. package/dist/fields/rich-text/json.js +44 -0
  75. package/dist/fields/select/component/Select.svelte +2 -2
  76. package/dist/fields/text/component/TextInline.svelte +113 -0
  77. package/dist/fields/text/component/TextInline.svelte.d.ts +14 -0
  78. package/dist/fields/textarea/component/TextArea.svelte +1 -1
  79. package/dist/fields/tree/component/TreeBlock.svelte +4 -4
  80. package/dist/index.d.ts +2 -9
  81. package/dist/index.js +3 -8
  82. package/dist/{panel/components/sections/live → live}/Consumer.svelte +2 -2
  83. package/dist/{panel/components/sections/live → live}/Consumer.svelte.d.ts +1 -1
  84. package/dist/{panel/components/sections/live → live}/LiveEdit.svelte +2 -2
  85. package/dist/{panel/components/sections/live → live}/Provider.svelte +1 -1
  86. package/dist/{panel/context/live.svelte.d.ts → live/context.svelte.d.ts} +1 -1
  87. package/dist/{panel/context/live.svelte.js → live/context.svelte.js} +3 -3
  88. package/dist/panel/components/fields/Error.svelte +1 -1
  89. package/dist/panel/components/fields/FieldsPreview.svelte +1 -1
  90. package/dist/panel/components/fields/FieldsPreviewTrigger.svelte +1 -1
  91. package/dist/panel/components/fields/Hint.svelte +1 -1
  92. package/dist/panel/components/fields/setting.css +6 -6
  93. package/dist/panel/components/sections/auth/AuthForm.svelte +2 -2
  94. package/dist/panel/components/sections/collection/Avatar.svelte +1 -1
  95. package/dist/panel/components/sections/collection/bulk-upload/FileDrop.svelte +1 -1
  96. package/dist/panel/components/sections/collection/bulk-upload/PageDrop.svelte +3 -3
  97. package/dist/panel/components/sections/collection/bulk-upload/UploadProgress.svelte +1 -1
  98. package/dist/panel/components/sections/collection/folder/Folder.svelte +1 -1
  99. package/dist/panel/components/sections/collection/folder/FolderWithActions.svelte +2 -2
  100. package/dist/panel/components/sections/collection/grid/grid-item/GridItem.svelte +1 -1
  101. package/dist/panel/components/sections/collection/header/DisplayMode.svelte +1 -2
  102. package/dist/panel/components/sections/collection/list/CollectionList.svelte +2 -2
  103. package/dist/panel/components/sections/collection/list/header/Header.svelte +1 -1
  104. package/dist/panel/components/sections/collection/list/row/Row.svelte +1 -1
  105. package/dist/panel/components/sections/collection/tree/CollectionTree.svelte +1 -1
  106. package/dist/panel/components/sections/collection/tree/CollectionTreeNode.svelte +1 -1
  107. package/dist/panel/components/sections/collection/upload-thumb-cell/UploadThumbCell.svelte +1 -1
  108. package/dist/panel/components/sections/commands/CommandButton.svelte +1 -1
  109. package/dist/panel/components/sections/document/AuthAPIKeyDialog.svelte +1 -1
  110. package/dist/panel/components/sections/document/AuthFooter.svelte +2 -2
  111. package/dist/panel/components/sections/document/AutoSaveBanner.svelte +2 -2
  112. package/dist/panel/components/sections/document/ButtonStatus.svelte +1 -1
  113. package/dist/panel/components/sections/document/ChangeSlugDialog.svelte +1 -1
  114. package/dist/panel/components/sections/document/DocumentChildren.svelte +2 -2
  115. package/dist/panel/components/sections/document/upload-header/UploadHeader.svelte +2 -2
  116. package/dist/panel/components/sections/document/upload-header/drop-zone/DropZone.svelte +1 -1
  117. package/dist/panel/components/sections/document/upload-header/drop-zone/DropZone.svelte.d.ts +1 -1
  118. package/dist/panel/components/sections/live/LiveFloatingUI.svelte +2 -2
  119. package/dist/panel/components/ui/breadcrumb/BreadCrumb.svelte +1 -1
  120. package/dist/panel/components/ui/button/button.svelte +3 -4
  121. package/dist/panel/components/ui/calendar/calendar.svelte.d.ts +1 -1
  122. package/dist/panel/components/ui/card/card.svelte +2 -2
  123. package/dist/panel/components/ui/card-document/card-document.svelte +2 -2
  124. package/dist/panel/components/ui/card-resource/card-resource.svelte +2 -2
  125. package/dist/panel/components/ui/checkbox/checkbox.svelte.d.ts +1 -1
  126. package/dist/panel/components/ui/command/command-input.svelte +1 -1
  127. package/dist/panel/components/ui/context-menu/context-menu.css +1 -1
  128. package/dist/panel/components/ui/dialog/dialog-content.css +1 -1
  129. package/dist/panel/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
  130. package/dist/panel/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +1 -1
  131. package/dist/panel/components/ui/dropdown-menu/dropdown-menu-content.css +1 -1
  132. package/dist/panel/components/ui/input/input.svelte +1 -1
  133. package/dist/panel/components/ui/nav/Nav.svelte +2 -3
  134. package/dist/panel/components/ui/nav/NavGroup.svelte +1 -1
  135. package/dist/panel/components/ui/nav/NavItemButton.svelte +1 -1
  136. package/dist/panel/components/ui/nav/UserButton.svelte +1 -1
  137. package/dist/panel/components/ui/page-header/PageHeader.svelte +1 -1
  138. package/dist/panel/components/ui/popover/popover-content.css +1 -1
  139. package/dist/panel/components/ui/radio-row-group/radio-row-group.css +0 -1
  140. package/dist/panel/components/ui/sheet/sheet.css +1 -1
  141. package/dist/panel/components/ui/sonner/sonner.css +1 -1
  142. package/dist/panel/components/ui/switch/switch.css +0 -2
  143. package/dist/panel/components/ui/switch/switch.svelte.d.ts +1 -1
  144. package/dist/panel/components/ui/tabs/tabs.css +2 -1
  145. package/dist/panel/components/ui/tag/tag.svelte +1 -1
  146. package/dist/panel/components/ui/tree/tree-rows.css +3 -3
  147. package/dist/panel/context/collection.svelte.d.ts +2 -2
  148. package/dist/panel/context/config.svelte.d.ts +2 -2
  149. package/dist/panel/context/documentForm.svelte.js +1 -1
  150. package/dist/panel/index.d.ts +3 -5
  151. package/dist/panel/index.js +4 -6
  152. package/dist/panel/pages/auth/forgot-password/ForgotPassword.svelte +1 -1
  153. package/dist/panel/pages/dashboard/Dashboard.svelte +1 -1
  154. package/dist/panel/pages/dashboard/DashboardCard.svelte +2 -2
  155. package/dist/panel/pages/dashboard/DashboardCollection.svelte +1 -1
  156. package/dist/panel/pages/dashboard/DashboardRow.svelte +1 -1
  157. package/dist/panel/public.d.ts +4 -0
  158. package/dist/panel/public.js +6 -0
  159. package/dist/panel/style/index.css +13 -37
  160. package/dist/panel/style/mixins/index.css +1 -1
  161. package/dist/panel/style/palette.css +3 -3
  162. package/dist/panel/style/scheme.css +10 -34
  163. package/dist/public.d.ts +13 -0
  164. package/dist/public.js +12 -0
  165. package/package.json +12 -6
  166. package/dist/fields/blocks/component/focus/RenderPlaceholder.svelte +0 -77
  167. package/dist/fields/blocks/component/focus/Stage.svelte +0 -190
  168. package/dist/fields/blocks/component/focus/Stage.svelte.d.ts +0 -8
  169. package/dist/fields/relation/client.d.ts +0 -28
  170. package/dist/fields/relation/client.js +0 -37
  171. /package/dist/{panel/util → fields/relation}/populate.d.ts +0 -0
  172. /package/dist/{panel/util → fields/relation}/populate.js +0 -0
  173. /package/dist/{panel/components/sections/live → live}/LiveEdit.svelte.d.ts +0 -0
  174. /package/dist/{panel/components/sections/live → live}/Provider.svelte.d.ts +0 -0
@@ -6,40 +6,55 @@ import { capitalize } from '../../../../util/string.js';
6
6
  import { getContext, setContext } from 'svelte';
7
7
  import { SvelteSet } from 'svelte/reactivity';
8
8
  const KEY = 'rime.blocks-focus';
9
- /** The query parameter that names the list focus is open on: `?focus=layout.sections`. */
9
+ /**
10
+ * The query parameter that names what focus is open on: a list, `?focus=layout.sections`, or one
11
+ * block and what it holds, `?focus=layout.sections.1`.
12
+ */
10
13
  export const FOCUS_PARAM = 'focus';
14
+ /** `sections.1` ends on a block, `sections` does not. */
15
+ const endsOnBlock = (target) => /\.\d+$/.test(target);
11
16
  /**
12
17
  * The focus mode of one blocks field: which list it is open on, which blocks are selected, which
13
18
  * rows are folded. The document form is the only state it writes to; a page reload with
14
19
  * `?focus=` in the address reopens it.
15
20
  *
16
- * The selection drives the stage: a block, or nothing, which is the root node of the layers and
17
- * shows the whole list.
21
+ * The stage shows the open list, or the one block the path ends on and the lists it holds. The
22
+ * selection is a block, or nothing, which is the root node of the layers.
18
23
  *
19
- * A focus opened from the field rides in `page.state`, so Escape, the close button and the back
20
- * button all do the same thing. One opened by the address has no history entry of its own: the
21
- * router is not ready for one while the page mounts, and closing it rewrites the address instead.
24
+ * A focus opened from the field rides in `page.state`, one history entry per `open`, so Escape
25
+ * and the back button go up the same levels. One opened by the address has no history entry of
26
+ * its own: the router is not ready for one while the page mounts, and going up rewrites the
27
+ * address instead.
22
28
  */
23
29
  export function setBlocksFocusContext(form) {
24
- let fromUrl = $state(page.url.searchParams.get(FOCUS_PARAM));
30
+ const initial = page.url.searchParams.get(FOCUS_PARAM);
31
+ let fromUrl = $state(initial);
25
32
  const path = $derived((page.state.blocksFocus ?? fromUrl) || null);
26
- let selection = $state([]);
33
+ /** Narrowed by the address, the block is selected and its fields show. */
34
+ const narrowedFromUrl = !!initial && endsOnBlock(initial);
35
+ let selection = $state(narrowedFromUrl ? [initial] : []);
27
36
  /** The panel's tab: the tree of blocks, the selected block's fields, the types to add. */
28
- let tab = $state('layers');
37
+ let tab = $state(narrowedFromUrl ? 'inspector' : 'layers');
29
38
  /** Counts the asks to show a block's fields, so a closed panel knows to open. */
30
39
  let inspected = $state(0);
31
40
  /** The picker of types to add is open. */
32
41
  let picking = $state(false);
33
42
  /** The folded rows, by block id: a fold stays on its block when the block moves. */
34
43
  const collapsed = new SvelteSet();
35
- let openedHere = false;
44
+ /** The entries focus pushed, up to the one on screen. */
45
+ const depth = $derived(page.state.blocksFocusDepth ?? 0);
36
46
  $effect(() => {
37
47
  if (path)
38
48
  return;
39
49
  selection = [];
40
50
  collapsed.clear();
51
+ // Closed back onto the entry the address opened: the address drops its focus too.
52
+ if (page.url.searchParams.has(FOCUS_PARAM))
53
+ replaceState(urlWith(null), {});
41
54
  });
42
55
  const locked = $derived(form.isDisabled || form.readOnly);
56
+ /** The block the path ends on, the one the stage is narrowed to. */
57
+ const narrowed = $derived(path && endsOnBlock(path) ? parseBlockPath(path) : null);
43
58
  const urlWith = (list) => {
44
59
  const url = new URL(page.url);
45
60
  if (list)
@@ -48,22 +63,44 @@ export function setBlocksFocusContext(form) {
48
63
  url.searchParams.delete(FOCUS_PARAM);
49
64
  return url;
50
65
  };
51
- function open(list, selected) {
52
- openedHere = true;
53
- fromUrl = null;
54
- pushState(urlWith(list), { blocksFocus: list });
55
- selection = selected ? [selected] : [];
66
+ /**
67
+ * Opens focus on a list, `sections`, with `selected` selected. Or narrows it to a block,
68
+ * `sections.1`: that block selected, its fields in the panel.
69
+ */
70
+ function open(target, selected) {
71
+ if (endsOnBlock(target)) {
72
+ selection = [target];
73
+ tab = 'inspector';
74
+ }
75
+ else {
76
+ selection = selected ? [selected] : [];
77
+ }
78
+ const state = { blocksFocus: target, blocksFocusDepth: depth + 1 };
79
+ pushState(urlWith(target), state);
56
80
  }
81
+ /** Leaves focus mode, back past every entry it pushed, the one the address opened included. */
57
82
  function close() {
58
- if (openedHere) {
59
- openedHere = false;
60
- history.back();
83
+ fromUrl = null;
84
+ if (depth) {
85
+ history.go(-depth);
61
86
  }
62
87
  else {
63
- fromUrl = null;
64
88
  replaceState(urlWith(null), {});
65
89
  }
66
90
  }
91
+ /** One level up: the entry before, the list of the narrowed block, or out of focus mode. */
92
+ function up() {
93
+ if (depth) {
94
+ history.back();
95
+ }
96
+ else if (narrowed) {
97
+ fromUrl = narrowed.list;
98
+ replaceState(urlWith(narrowed.list), { blocksFocus: narrowed.list });
99
+ }
100
+ else {
101
+ close();
102
+ }
103
+ }
67
104
  /* ---------------------------------------------------------------- rows */
68
105
  const builderOf = (list) => form.blocks.builder(list);
69
106
  const configOf = (list, block) => builderOf(list)?.get.blocks.find((candidate) => candidate.name === block.type)?.get;
@@ -103,37 +140,36 @@ export function setBlocksFocusContext(form) {
103
140
  };
104
141
  });
105
142
  }
106
- /** Every row, folded ones included, in layers order. */
107
- function allRows() {
143
+ /** The rows the stage starts from: the open list's, or the one block the path ends on. */
144
+ function rootRows() {
108
145
  if (!path)
109
146
  return [];
110
- const out = [];
111
- const walk = (list, depth) => {
112
- for (const row of rowsOf(list, depth)) {
113
- out.push(row);
114
- for (const child of row.children)
115
- walk(child.list, depth + 1);
116
- }
117
- };
118
- walk(path, 0);
147
+ if (!narrowed)
148
+ return rowsOf(path);
149
+ return rowsOf(narrowed.list).filter((row) => row.index === narrowed.index);
150
+ }
151
+ /** The rows from the root down, each followed by the rows of its lists. */
152
+ function walk(rows, skipFolded, out = []) {
153
+ for (const row of rows) {
154
+ out.push(row);
155
+ if (skipFolded && collapsed.has(row.block.id))
156
+ continue;
157
+ for (const child of row.children)
158
+ walk(rowsOf(child.list, row.depth + 1), skipFolded, out);
159
+ }
119
160
  return out;
120
161
  }
162
+ /** Every row, folded ones included, in layers order. */
163
+ const allRows = () => walk(rootRows(), false);
121
164
  /** The rows on screen: what the arrow keys walk. */
122
- function visibleRows() {
165
+ const visibleRows = () => walk(rootRows(), true);
166
+ /** Where a block goes with nothing selected: the open list, or the narrowed block's first list. */
167
+ function homeList() {
123
168
  if (!path)
124
- return [];
125
- const out = [];
126
- const walk = (list, depth) => {
127
- for (const row of rowsOf(list, depth)) {
128
- out.push(row);
129
- if (collapsed.has(row.block.id))
130
- continue;
131
- for (const child of row.children)
132
- walk(child.list, depth + 1);
133
- }
134
- };
135
- walk(path, 0);
136
- return out;
169
+ return null;
170
+ if (!narrowed)
171
+ return path;
172
+ return rootRows()[0]?.children[0]?.list ?? null;
137
173
  }
138
174
  /**
139
175
  * The root node of the layers: the blocks field's label, or the block the open list hangs off,
@@ -142,6 +178,8 @@ export function setBlocksFocusContext(form) {
142
178
  function rootLabel() {
143
179
  if (!path)
144
180
  return '';
181
+ if (narrowed)
182
+ return rootRows()[0]?.title ?? '';
145
183
  const builder = builderOf(path);
146
184
  const fieldLabel = builder?.get.label || capitalize(builder?.name || '');
147
185
  const crumbs = breadcrumb();
@@ -185,7 +223,7 @@ export function setBlocksFocusContext(form) {
185
223
  }
186
224
  /** The lists a block of `type` may go to, the one it is in and its own subtree left out. */
187
225
  function moveTargets() {
188
- const current = currentAt();
226
+ const current = movableAt();
189
227
  if (!path || !current)
190
228
  return [];
191
229
  const currentPath = blockPath(current);
@@ -194,7 +232,7 @@ export function setBlocksFocusContext(form) {
194
232
  return [];
195
233
  const rootBuilder = builderOf(path);
196
234
  const targets = [];
197
- if (form.blocks.accepts(path, type) && current.list !== path) {
235
+ if (!narrowed && form.blocks.accepts(path, type) && current.list !== path) {
198
236
  targets.push({
199
237
  list: path,
200
238
  label: rootBuilder?.get.label || capitalize(rootBuilder?.name || '')
@@ -242,14 +280,24 @@ export function setBlocksFocusContext(form) {
242
280
  selection = [];
243
281
  };
244
282
  /* ---------------------------------------------------------- operations */
245
- /** Where the palette inserts: after the current block, else at the end of the open list. */
246
- function insertionPoint() {
283
+ /** The narrowed block itself: it stays where it is, and what is added goes inside it. */
284
+ const isNarrowedBlock = (rowPath) => !!narrowed && rowPath === path;
285
+ /** The block the operations act on: the selected one, unless it is the narrowed block. */
286
+ function movableAt() {
247
287
  const current = currentAt();
288
+ return current && !isNarrowedBlock(blockPath(current)) ? current : null;
289
+ }
290
+ /** The list the next insert goes to: the selected block's, else the home list. */
291
+ const insertList = () => movableAt()?.list ?? homeList();
292
+ /** Where the palette inserts: after the selected block, else at the end of the home list. */
293
+ function insertionPoint() {
294
+ const current = movableAt();
248
295
  if (current)
249
296
  return { list: current.list, index: current.index + 1 };
250
- if (!path)
297
+ const list = homeList();
298
+ if (!list)
251
299
  return null;
252
- return { list: path, index: form.blocks.list(path).length };
300
+ return { list, index: form.blocks.list(list).length };
253
301
  }
254
302
  function insertType(type, at = insertionPoint()) {
255
303
  if (locked || !at)
@@ -263,7 +311,7 @@ export function setBlocksFocusContext(form) {
263
311
  return id;
264
312
  }
265
313
  function duplicateSelection() {
266
- const current = currentAt();
314
+ const current = movableAt();
267
315
  if (locked || !current)
268
316
  return;
269
317
  form.blocks.duplicate(blockPath(current));
@@ -274,7 +322,11 @@ export function setBlocksFocusContext(form) {
274
322
  if (locked || !selection.length)
275
323
  return;
276
324
  const order = visibleRows().map((row) => row.path);
277
- const targets = [...selection].sort((a, b) => order.indexOf(b) - order.indexOf(a));
325
+ const targets = selection
326
+ .filter((rowPath) => !isNarrowedBlock(rowPath))
327
+ .sort((a, b) => order.indexOf(b) - order.indexOf(a));
328
+ if (!targets.length)
329
+ return;
278
330
  for (const target of targets)
279
331
  form.blocks.remove(target);
280
332
  const first = parseBlockPath(targets[targets.length - 1]);
@@ -286,7 +338,7 @@ export function setBlocksFocusContext(form) {
286
338
  select(blockPath({ list: first.list, index: Math.min(first.index, remaining.length - 1) }));
287
339
  }
288
340
  function moveSelection(delta) {
289
- const current = currentAt();
341
+ const current = movableAt();
290
342
  if (locked || !current)
291
343
  return;
292
344
  const index = current.index + delta;
@@ -296,7 +348,7 @@ export function setBlocksFocusContext(form) {
296
348
  select(blockPath({ list: current.list, index }));
297
349
  }
298
350
  function moveSelectionInto(list) {
299
- const current = currentAt();
351
+ const current = movableAt();
300
352
  if (locked || !current)
301
353
  return;
302
354
  const index = form.blocks.list(list).length;
@@ -384,9 +436,9 @@ export function setBlocksFocusContext(form) {
384
436
  if (!locked)
385
437
  picking = true;
386
438
  },
387
- /** The open list's block set has at least one render: the stage is the stack of renders. */
388
- get hasRenders() {
389
- return !!path && !!builderOf(path)?.get.blocks.some((block) => block.get.render);
439
+ /** The block the stage is narrowed to, when the path ends on one. */
440
+ get narrowedRow() {
441
+ return narrowed ? (rootRows()[0] ?? null) : null;
390
442
  },
391
443
  /** Nothing selected: the root node, and the whole list on the stage. */
392
444
  get rootSelected() {
@@ -396,10 +448,15 @@ export function setBlocksFocusContext(form) {
396
448
  selectRoot,
397
449
  breadcrumb,
398
450
  isSelected: (rowPath) => selection.includes(rowPath),
451
+ isNarrowedBlock,
452
+ insertList,
399
453
  isCollapsed,
400
454
  open,
401
455
  close,
456
+ up,
402
457
  rowsOf,
458
+ rootRows,
459
+ homeList,
403
460
  allRows,
404
461
  visibleRows,
405
462
  moveTargets,
@@ -27,7 +27,7 @@
27
27
  <span class="rz-block-tile__description">{block.description}</span>
28
28
  {/if}
29
29
 
30
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
30
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
31
31
 
32
32
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
33
33
 
@@ -52,7 +52,7 @@
52
52
  background-color: var(--rz-bg-raised);
53
53
  box-shadow:
54
54
  0 0 0 1px var(--rz-border),
55
- var(--rz-shadow-raised);
55
+ var(--rz-shadow-raised, 0 0 transparent);
56
56
  border-radius: var(--rz-radius-xl);
57
57
  color: var(--rz-fg-muted);
58
58
  font-size: var(--rz-text-sm);
@@ -68,9 +68,7 @@
68
68
  }
69
69
 
70
70
  :global(.rz-block-tile[data-selected]) {
71
- box-shadow:
72
- 0 0 0 1px var(--rz-accent-border),
73
- var(--rz-shadow-raised);
71
+ box-shadow: 0 0 0 1px var(--rz-accent-border);
74
72
  }
75
73
 
76
74
  :global(.rz-block-tile:focus-visible) {
@@ -90,7 +90,7 @@
90
90
  <Field.Error error={field.error} />
91
91
  </fieldset>
92
92
 
93
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
93
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
94
94
 
95
95
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
96
96
 
@@ -14,7 +14,7 @@
14
14
  background-color: var(--rz-bg-float);
15
15
  box-shadow:
16
16
  0 0 0 1px var(--rz-border),
17
- var(--rz-shadow-float);
17
+ var(--rz-shadow-float, 0 0 transparent);
18
18
  padding: var(--rz-size-1);
19
19
  .rz-command-input {
20
20
  padding-top: 0;
@@ -78,7 +78,7 @@
78
78
  <Field.Error error={field.error} />
79
79
  </fieldset>
80
80
 
81
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
81
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
82
82
 
83
83
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
84
84
 
@@ -87,7 +87,7 @@
87
87
  {/if}
88
88
  </div>
89
89
 
90
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
90
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
91
91
 
92
92
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
93
93
 
@@ -106,15 +106,15 @@
106
106
  background-color: var(--rz-bg-raised);
107
107
  box-shadow:
108
108
  0 0 0 1px var(--rz-border),
109
- var(--rz-shadow-raised);
109
+ var(--rz-shadow-raised, 0 0 transparent);
110
110
  border-radius: var(--rz-radius-xl);
111
111
  }
112
112
 
113
- /* Inside a group, a block, a tree item or a stage card: a hairline, no second fill. */
113
+ /* Inside a group, a block, a tree item or the inspector: a hairline, no second fill. */
114
114
  :global(.rz-group-field__content) .rz-group-field__wrapper,
115
115
  :global(.rz-block__fields) .rz-group-field__wrapper,
116
116
  :global(.rz-tree-item__fields) .rz-group-field__wrapper,
117
- :global(.rz-stage__fields) .rz-group-field__wrapper {
117
+ :global(.rz-inspector__fields) .rz-group-field__wrapper {
118
118
  background-color: transparent;
119
119
  box-shadow: 0 0 0 1px var(--rz-border);
120
120
  }
@@ -181,7 +181,7 @@
181
181
  <Field.Error error={field.error} />
182
182
  </fieldset>
183
183
 
184
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
184
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
185
185
 
186
186
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
187
187
 
@@ -127,7 +127,7 @@
127
127
  </Command.Root>
128
128
  </div>
129
129
 
130
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
130
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
131
131
 
132
132
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
133
133
 
@@ -158,7 +158,7 @@
158
158
  background-color: var(--rz-bg-float);
159
159
  box-shadow:
160
160
  0 0 0 1px var(--rz-border),
161
- var(--rz-shadow-float);
161
+ var(--rz-shadow-float, 0 0 transparent);
162
162
  border-radius: var(--rz-radius-md);
163
163
  position: absolute;
164
164
  left: 0;
@@ -56,7 +56,7 @@ export declare class LinkFieldBuilder extends FormFieldBuilder<LinkField> {
56
56
  root?: boolean | undefined;
57
57
  index?: boolean | undefined;
58
58
  layout: "compact" | "default";
59
- types: "tel"[] | "email"[] | "url"[] | "anchor"[];
59
+ types: "url"[] | "email"[] | "tel"[] | "anchor"[];
60
60
  } & {
61
61
  component: import("svelte").Component<any>;
62
62
  cell?: import("svelte").Component<{
@@ -45,7 +45,7 @@
45
45
  <Field.Error error={field.error} />
46
46
  </fieldset>
47
47
 
48
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
48
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
49
49
 
50
50
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
51
51
 
@@ -42,7 +42,7 @@
42
42
  mimeType={ressource.data.doc.mimeType}
43
43
  url="{env.PUBLIC_RIME_URL}{ressource.data.doc._thumbnail}"
44
44
  />
45
- {ressource.data.doc.title}
45
+ <span class="rz-relation-cell__title">{ressource.data.doc.title}</span>
46
46
  {:else}
47
47
  <span class="rz-relation-cell__title">{ressource.data.doc.title}</span>
48
48
  {/if}
@@ -51,6 +51,8 @@
51
51
 
52
52
  <style>
53
53
  .rz-relation-cell {
54
+ --rz-upload-preview-cell-size: var(--rz-size-7);
55
+ --rz-upload-preview-cell-fit: cover;
54
56
  display: flex;
55
57
  gap: var(--rz-size-2);
56
58
  align-items: center;
@@ -9,9 +9,9 @@
9
9
  import { getConfigContext } from '../../../panel/context/config.svelte.js';
10
10
  import type { DocumentFormContext } from '../../../panel/context/documentForm.svelte.js';
11
11
  import { getLocaleContext } from '../../../panel/context/locale.svelte.js';
12
- import { populate } from '../../../panel/util/populate.js';
12
+ import { populate } from '../populate.js';
13
13
  import { uploadFiles, type UploadProgress } from '../../../panel/util/upload-file.js';
14
- import { Image as ImageIcon } from '@lucide/svelte';
14
+ import { Image as ImageIcon, Trash2 } from '@lucide/svelte';
15
15
  import type { Snippet } from 'svelte';
16
16
  import { SvelteMap } from 'svelte/reactivity';
17
17
  import type { Relation, RelationFieldBuilder } from '../index.js';
@@ -122,17 +122,21 @@
122
122
  {#if !field.editable}
123
123
  <!-- Read-only: drawn, never picked. -->
124
124
  {:else}
125
+ <!-- One bar, bottom right: replace or edit the selection, then remove. -->
125
126
  <div class="rz-relation-inline__controls">
126
- {#if many}
127
- <button type="button" onclick={(event) => control(event, open)}>
128
- {t__('fields.edit_selection')}
129
- </button>
130
- {:else}
131
- <button type="button" onclick={(event) => control(event, open)}>
132
- {t__('fields.replace')}
133
- </button>
134
- <button type="button" onclick={(event) => control(event, () => onChange([], []))}>
135
- {t__('fields.remove')}
127
+ <button type="button" onclick={(event) => control(event, open)}>
128
+ {many ? t__('fields.edit_selection') : t__('fields.replace')}
129
+ </button>
130
+ {#if !many}
131
+ <span class="rz-relation-inline__separator" aria-hidden="true"></span>
132
+ <button
133
+ type="button"
134
+ class="rz-relation-inline__icon"
135
+ title={t__('fields.remove')}
136
+ aria-label={t__('fields.remove')}
137
+ onclick={(event) => control(event, () => onChange([], []))}
138
+ >
139
+ <Trash2 size={14} />
136
140
  </button>
137
141
  {/if}
138
142
  </div>
@@ -195,7 +199,7 @@
195
199
  />
196
200
  {/if}
197
201
 
198
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
202
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
199
203
 
200
204
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
201
205
 
@@ -214,28 +218,55 @@
214
218
  position: relative;
215
219
  }
216
220
 
217
- /* Over the drawn docs, top right: on a selected block, or under the pointer. */
221
+ /*
222
+ * Over the drawn docs, bottom right, clear of the block's own bar on top: on a selected block,
223
+ * or under the pointer.
224
+ */
218
225
  .rz-relation-inline__controls {
226
+ background-color: var(--rz-bg-float);
227
+ box-shadow:
228
+ 0 0 0 1px var(--rz-border),
229
+ var(--rz-shadow-float, 0 0 transparent);
219
230
  position: absolute;
220
- top: var(--rz-size-2);
221
231
  right: var(--rz-size-2);
232
+ bottom: var(--rz-size-2);
222
233
  z-index: 2;
223
234
  display: none;
224
- gap: var(--rz-size-1);
235
+ align-items: center;
236
+ gap: var(--rz-size-0-5);
237
+ padding: var(--rz-size-0-5);
238
+ border-radius: var(--rz-radius-lg);
225
239
 
226
240
  button {
227
241
  height: var(--rz-size-7);
228
- padding-inline: var(--rz-size-3);
242
+ padding-inline: var(--rz-size-2-5);
229
243
  border-radius: var(--rz-radius-md);
230
- background-color: var(--rz-bg-inverse);
231
- color: var(--rz-fg-inverse);
244
+ color: var(--rz-fg-muted);
232
245
  font-size: var(--rz-text-xs);
233
246
  &:hover {
234
- filter: brightness(1.12);
247
+ background-image: linear-gradient(var(--rz-bg-hover) 0 0);
248
+ color: var(--rz-fg);
249
+ }
250
+ &:focus-visible {
251
+ outline: 2px solid var(--rz-accent-border);
252
+ outline-offset: 2px;
235
253
  }
236
254
  }
237
255
  }
238
256
 
257
+ .rz-relation-inline__icon {
258
+ display: grid;
259
+ place-items: center;
260
+ width: var(--rz-size-7);
261
+ padding: 0;
262
+ }
263
+
264
+ .rz-relation-inline__separator {
265
+ width: 1px;
266
+ height: var(--rz-size-4);
267
+ background-color: var(--rz-border);
268
+ }
269
+
239
270
  .rz-relation-inline:hover .rz-relation-inline__controls,
240
271
  :global(.rz-renders__item[data-selected]) .rz-relation-inline__controls {
241
272
  display: flex;
@@ -171,7 +171,7 @@
171
171
  {/if}
172
172
  </div>
173
173
 
174
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
174
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
175
175
 
176
176
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
177
177
 
@@ -199,7 +199,7 @@
199
199
  background-color: var(--rz-bg-float);
200
200
  box-shadow:
201
201
  0 0 0 1px var(--rz-border),
202
- var(--rz-shadow-float);
202
+ var(--rz-shadow-float, 0 0 transparent);
203
203
  position: absolute;
204
204
  left: 0;
205
205
  right: 0;
@@ -272,7 +272,7 @@
272
272
  background-color: var(--rz-bg-raised);
273
273
  box-shadow:
274
274
  0 0 0 1px var(--rz-border),
275
- var(--rz-shadow-raised);
275
+ var(--rz-shadow-raised, 0 0 transparent);
276
276
  display: inline-flex;
277
277
  align-items: center;
278
278
  gap: var(--rz-size-0-5);
@@ -472,7 +472,7 @@
472
472
  onCancel={() => onCreating?.(false)}
473
473
  />
474
474
 
475
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
475
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
476
476
 
477
477
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
478
478
 
@@ -576,7 +576,7 @@
576
576
  background-color: var(--rz-bg-raised);
577
577
  box-shadow:
578
578
  0 0 0 1px var(--rz-border),
579
- var(--rz-shadow-raised);
579
+ var(--rz-shadow-raised, 0 0 transparent);
580
580
  display: flex;
581
581
  align-items: center;
582
582
  gap: var(--rz-size-2-5);
@@ -180,7 +180,7 @@
180
180
  onCreating={(creating) => (creating ? onRelationCreation() : onRelationCreationCanceled())}
181
181
  />
182
182
 
183
- <style>/* A level: its fill, a hairline, its shadow. raised, float. */
183
+ <style>/* A level: its fill, a hairline, and its shadow when it has one. raised has none, float does. */
184
184
 
185
185
  /* A well: an input, a track, a dropzone. Its edge shows on hover. */
186
186
 
@@ -293,7 +293,7 @@
293
293
  background-color: var(--rz-bg-float);
294
294
  box-shadow:
295
295
  0 0 0 1px var(--rz-border),
296
- var(--rz-shadow-float);
296
+ var(--rz-shadow-float, 0 0 transparent);
297
297
  position: absolute;
298
298
  z-index: 1;
299
299
  top: var(--rz-size-1);