@velastack/cms 0.4.0 → 0.5.1

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 (205) hide show
  1. package/README.md +140 -28
  2. package/dist/backend/index.d.ts +1 -1
  3. package/dist/backend/routes/seed.d.ts +6 -0
  4. package/dist/backend/routes/seed.js +96 -0
  5. package/dist/backend/routes/table.js +3 -0
  6. package/dist/backend/store/queries.d.ts +6 -1
  7. package/dist/backend/store/queries.js +89 -0
  8. package/dist/cms.js +13 -16
  9. package/dist/components/admin-bar/admin-bar-internal.svelte +4 -4
  10. package/dist/components/admin-bar/admin-bar.css +1 -1
  11. package/dist/components/admin-bar/field-input.svelte +239 -0
  12. package/dist/components/admin-bar/field-input.svelte.d.ts +30 -0
  13. package/dist/components/admin-bar/fields-form.svelte +218 -0
  14. package/dist/components/admin-bar/fields-form.svelte.d.ts +16 -0
  15. package/dist/components/admin-bar/icons/pencil.svelte +19 -0
  16. package/dist/components/admin-bar/icons/pencil.svelte.d.ts +3 -0
  17. package/dist/components/admin-bar/keyboard-shortcuts-dialog.svelte +1 -1
  18. package/dist/components/admin-bar/link-input.svelte +102 -0
  19. package/dist/components/admin-bar/link-input.svelte.d.ts +9 -0
  20. package/dist/components/admin-bar/list-field.svelte +93 -0
  21. package/dist/components/admin-bar/list-field.svelte.d.ts +20 -0
  22. package/dist/components/admin-bar/locales-panel.svelte +42 -1
  23. package/dist/components/admin-bar/page-config.d.ts +8 -1
  24. package/dist/components/admin-bar/page-config.js +15 -2
  25. package/dist/components/admin-bar/page-options.d.ts +24 -0
  26. package/dist/components/admin-bar/page-options.js +53 -0
  27. package/dist/components/admin-bar/reorder-buttons.svelte +52 -0
  28. package/dist/components/admin-bar/reorder-buttons.svelte.d.ts +10 -0
  29. package/dist/components/admin-bar/rich-text-input.svelte +92 -0
  30. package/dist/components/admin-bar/rich-text-input.svelte.d.ts +9 -0
  31. package/dist/components/admin-bar/seo-panel.svelte +144 -189
  32. package/dist/components/admin-bar/site-options-panel.svelte +115 -0
  33. package/dist/components/admin-bar/site-options-panel.svelte.d.ts +7 -0
  34. package/dist/components/admin-bar/structured-editor-popover.svelte +259 -0
  35. package/dist/components/admin-bar/structured-editor-popover.svelte.d.ts +48 -0
  36. package/dist/components/admin-bar/ui/checkbox/checkbox.svelte +36 -0
  37. package/dist/components/admin-bar/ui/checkbox/checkbox.svelte.d.ts +5 -0
  38. package/dist/components/admin-bar/ui/checkbox/index.d.ts +2 -0
  39. package/dist/components/admin-bar/ui/checkbox/index.js +2 -0
  40. package/dist/components/admin-bar/ui/popover/index.d.ts +5 -0
  41. package/dist/components/admin-bar/ui/popover/index.js +7 -0
  42. package/dist/components/admin-bar/ui/popover/popover-close.svelte +7 -0
  43. package/dist/components/admin-bar/ui/popover/popover-close.svelte.d.ts +4 -0
  44. package/dist/components/admin-bar/ui/popover/popover-content.svelte +35 -0
  45. package/dist/components/admin-bar/ui/popover/popover-content.svelte.d.ts +10 -0
  46. package/dist/components/admin-bar/ui/popover/popover-trigger.svelte +7 -0
  47. package/dist/components/admin-bar/ui/popover/popover-trigger.svelte.d.ts +4 -0
  48. package/dist/components/admin-bar/ui/popover/popover.svelte +7 -0
  49. package/dist/components/admin-bar/ui/popover/popover.svelte.d.ts +3 -0
  50. package/dist/components/admin-bar/ui/sheet/index.d.ts +10 -0
  51. package/dist/components/admin-bar/ui/sheet/index.js +12 -0
  52. package/dist/components/admin-bar/ui/sheet/sheet-close.svelte +7 -0
  53. package/dist/components/admin-bar/ui/sheet/sheet-close.svelte.d.ts +4 -0
  54. package/dist/components/admin-bar/ui/sheet/sheet-content.svelte +64 -0
  55. package/dist/components/admin-bar/ui/sheet/sheet-content.svelte.d.ts +17 -0
  56. package/dist/components/admin-bar/ui/sheet/sheet-description.svelte +17 -0
  57. package/dist/components/admin-bar/ui/sheet/sheet-description.svelte.d.ts +4 -0
  58. package/dist/components/admin-bar/ui/sheet/sheet-footer.svelte +23 -0
  59. package/dist/components/admin-bar/ui/sheet/sheet-footer.svelte.d.ts +5 -0
  60. package/dist/components/admin-bar/ui/sheet/sheet-header.svelte +20 -0
  61. package/dist/components/admin-bar/ui/sheet/sheet-header.svelte.d.ts +5 -0
  62. package/dist/components/admin-bar/ui/sheet/sheet-overlay.svelte +20 -0
  63. package/dist/components/admin-bar/ui/sheet/sheet-overlay.svelte.d.ts +4 -0
  64. package/dist/components/admin-bar/ui/sheet/sheet-title.svelte +17 -0
  65. package/dist/components/admin-bar/ui/sheet/sheet-title.svelte.d.ts +4 -0
  66. package/dist/components/admin-bar/ui/sheet/sheet-trigger.svelte +7 -0
  67. package/dist/components/admin-bar/ui/sheet/sheet-trigger.svelte.d.ts +4 -0
  68. package/dist/components/admin-bar/ui/sheet/sheet.svelte +7 -0
  69. package/dist/components/admin-bar/ui/sheet/sheet.svelte.d.ts +3 -0
  70. package/dist/components/admin-bar/ui/switch/index.d.ts +2 -0
  71. package/dist/components/admin-bar/ui/switch/index.js +2 -0
  72. package/dist/components/admin-bar/ui/switch/switch.svelte +27 -0
  73. package/dist/components/admin-bar/ui/switch/switch.svelte.d.ts +5 -0
  74. package/dist/components/admin-bar/ui/tabs/index.d.ts +5 -0
  75. package/dist/components/admin-bar/ui/tabs/index.js +7 -0
  76. package/dist/components/admin-bar/ui/tabs/tabs-content.svelte +17 -0
  77. package/dist/components/admin-bar/ui/tabs/tabs-content.svelte.d.ts +4 -0
  78. package/dist/components/admin-bar/ui/tabs/tabs-list.svelte +16 -0
  79. package/dist/components/admin-bar/ui/tabs/tabs-list.svelte.d.ts +4 -0
  80. package/dist/components/admin-bar/ui/tabs/tabs-trigger.svelte +20 -0
  81. package/dist/components/admin-bar/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
  82. package/dist/components/admin-bar/ui/tabs/tabs.svelte +19 -0
  83. package/dist/components/admin-bar/ui/tabs/tabs.svelte.d.ts +4 -0
  84. package/dist/components/cms/cms-boolean.svelte +9 -18
  85. package/dist/components/cms/cms-boolean.svelte.d.ts +1 -0
  86. package/dist/components/cms/cms-collection.svelte +46 -0
  87. package/dist/components/cms/cms-collection.svelte.d.ts +25 -0
  88. package/dist/components/cms/cms-contact.svelte +26 -0
  89. package/dist/components/cms/cms-contact.svelte.d.ts +14 -0
  90. package/dist/components/cms/cms-date-time-editable.svelte +3 -3
  91. package/dist/components/cms/cms-date-time.svelte +17 -17
  92. package/dist/components/cms/cms-date-time.svelte.d.ts +2 -1
  93. package/dist/components/cms/cms-faq.svelte +22 -0
  94. package/dist/components/cms/cms-faq.svelte.d.ts +15 -0
  95. package/dist/components/cms/cms-gallery.svelte +31 -0
  96. package/dist/components/cms/cms-gallery.svelte.d.ts +16 -0
  97. package/dist/components/cms/cms-hours-editable.svelte +160 -0
  98. package/dist/components/cms/cms-hours-editable.svelte.d.ts +17 -0
  99. package/dist/components/cms/cms-hours.svelte +42 -0
  100. package/dist/components/cms/cms-hours.svelte.d.ts +19 -0
  101. package/dist/components/cms/cms-image-editable.svelte +13 -4
  102. package/dist/components/cms/cms-image.svelte +19 -34
  103. package/dist/components/cms/cms-image.svelte.d.ts +7 -8
  104. package/dist/components/cms/cms-link-editable.svelte +4 -60
  105. package/dist/components/cms/cms-link.svelte +15 -28
  106. package/dist/components/cms/cms-link.svelte.d.ts +4 -9
  107. package/dist/components/cms/cms-logos.svelte +29 -0
  108. package/dist/components/cms/cms-logos.svelte.d.ts +16 -0
  109. package/dist/components/cms/cms-markdown.svelte +15 -16
  110. package/dist/components/cms/cms-markdown.svelte.d.ts +1 -0
  111. package/dist/components/cms/cms-nav.svelte +22 -0
  112. package/dist/components/cms/cms-nav.svelte.d.ts +15 -0
  113. package/dist/components/cms/cms-number-editable.svelte +4 -2
  114. package/dist/components/cms/cms-number.svelte +18 -17
  115. package/dist/components/cms/cms-number.svelte.d.ts +2 -1
  116. package/dist/components/cms/cms-pricing.svelte +36 -0
  117. package/dist/components/cms/cms-pricing.svelte.d.ts +14 -0
  118. package/dist/components/cms/cms-rich-text.svelte +17 -22
  119. package/dist/components/cms/cms-rich-text.svelte.d.ts +1 -0
  120. package/dist/components/cms/cms-schedule.svelte +35 -0
  121. package/dist/components/cms/cms-schedule.svelte.d.ts +16 -0
  122. package/dist/components/cms/cms-social-links.svelte +35 -0
  123. package/dist/components/cms/cms-social-links.svelte.d.ts +15 -0
  124. package/dist/components/cms/cms-stats.svelte +31 -0
  125. package/dist/components/cms/cms-stats.svelte.d.ts +16 -0
  126. package/dist/components/cms/cms-steps.svelte +31 -0
  127. package/dist/components/cms/cms-steps.svelte.d.ts +16 -0
  128. package/dist/components/cms/cms-store.svelte.d.ts +14 -0
  129. package/dist/components/cms/cms-store.svelte.js +39 -2
  130. package/dist/components/cms/cms-structured-editable.svelte +29 -0
  131. package/dist/components/cms/{cms-repeater-editable.svelte.d.ts → cms-structured-editable.svelte.d.ts} +12 -8
  132. package/dist/components/cms/cms-structured.svelte +50 -0
  133. package/dist/components/cms/cms-structured.svelte.d.ts +49 -0
  134. package/dist/components/cms/cms-team.svelte +22 -0
  135. package/dist/components/cms/cms-team.svelte.d.ts +15 -0
  136. package/dist/components/cms/cms-testimonials.svelte +36 -0
  137. package/dist/components/cms/cms-testimonials.svelte.d.ts +15 -0
  138. package/dist/components/cms/cms-text.svelte +23 -30
  139. package/dist/components/cms/cms-text.svelte.d.ts +3 -1
  140. package/dist/components/cms/cms-timeline.svelte +35 -0
  141. package/dist/components/cms/cms-timeline.svelte.d.ts +16 -0
  142. package/dist/components/cms/index.d.ts +15 -1
  143. package/dist/components/cms/index.js +15 -1
  144. package/dist/components/cms/scope.d.ts +25 -5
  145. package/dist/components/cms/use-cms-field.svelte.d.ts +54 -0
  146. package/dist/components/cms/use-cms-field.svelte.js +83 -0
  147. package/dist/core/metadata.d.ts +44 -0
  148. package/dist/core/metadata.js +59 -0
  149. package/dist/core/shapes/collection.d.ts +60 -0
  150. package/dist/core/shapes/collection.js +194 -0
  151. package/dist/core/shapes/contact.d.ts +70 -0
  152. package/dist/core/shapes/contact.js +214 -0
  153. package/dist/core/shapes/faq.d.ts +12 -0
  154. package/dist/core/shapes/faq.js +48 -0
  155. package/dist/core/shapes/form.d.ts +46 -0
  156. package/dist/core/shapes/form.js +7 -0
  157. package/dist/core/shapes/hours.d.ts +91 -0
  158. package/dist/core/shapes/hours.js +325 -0
  159. package/dist/core/shapes/image.d.ts +15 -0
  160. package/dist/core/shapes/image.js +27 -0
  161. package/dist/core/shapes/index.d.ts +31 -0
  162. package/dist/core/shapes/index.js +13 -0
  163. package/dist/core/shapes/link.d.ts +15 -0
  164. package/dist/core/shapes/link.js +42 -0
  165. package/dist/core/shapes/nav.d.ts +21 -0
  166. package/dist/core/shapes/nav.js +71 -0
  167. package/dist/core/shapes/presets.d.ts +65 -0
  168. package/dist/core/shapes/presets.js +152 -0
  169. package/dist/core/shapes/pricing.d.ts +40 -0
  170. package/dist/core/shapes/pricing.js +115 -0
  171. package/dist/core/shapes/social-links.d.ts +19 -0
  172. package/dist/core/shapes/social-links.js +83 -0
  173. package/dist/core/shapes/team.d.ts +22 -0
  174. package/dist/core/shapes/team.js +70 -0
  175. package/dist/core/shapes/testimonials.d.ts +24 -0
  176. package/dist/core/shapes/testimonials.js +86 -0
  177. package/dist/core/structured-registry.d.ts +10 -0
  178. package/dist/core/structured-registry.js +6 -0
  179. package/dist/core/structured.d.ts +131 -0
  180. package/dist/core/structured.js +235 -0
  181. package/dist/core/wire.d.ts +31 -1
  182. package/dist/editor.d.ts +26 -0
  183. package/dist/editor.js +25 -0
  184. package/dist/index.d.ts +26 -4
  185. package/dist/index.js +31 -2
  186. package/dist/server/load-cms.js +1 -0
  187. package/package.json +12 -3
  188. package/plugin/README.md +22 -2
  189. package/plugin/dist/build.d.ts +74 -0
  190. package/plugin/dist/build.js +210 -0
  191. package/plugin/dist/build.js.map +1 -0
  192. package/plugin/dist/index.d.ts +10 -1
  193. package/plugin/dist/index.js +63 -4
  194. package/plugin/dist/index.js.map +1 -1
  195. package/plugin/dist/manifest.d.ts +31 -0
  196. package/plugin/dist/manifest.js +146 -41
  197. package/plugin/dist/manifest.js.map +1 -1
  198. package/plugin/dist/parse-svelte.d.ts +15 -0
  199. package/plugin/dist/parse-svelte.js +26 -16
  200. package/plugin/dist/parse-svelte.js.map +1 -1
  201. package/dist/components/admin-bar/site-settings-panel.svelte +0 -225
  202. package/dist/components/admin-bar/site-settings-panel.svelte.d.ts +0 -7
  203. package/dist/components/cms/cms-repeater-editable.svelte +0 -137
  204. package/dist/components/cms/cms-repeater.svelte +0 -55
  205. package/dist/components/cms/cms-repeater.svelte.d.ts +0 -30
@@ -1,46 +1,32 @@
1
1
  <script lang="ts" module>
2
- export type CmsLinkValue = {
3
- label?: string;
4
- /** Raw URL — used when `routeId` is absent. */
5
- href?: string;
6
- /** SvelteKit route id, e.g. `/blog/[slug]`. Resolved at render time. */
7
- routeId?: string;
8
- params?: Record<string, string>;
9
- newTab?: boolean;
10
- };
2
+ export { normalizeLink } from '../../core/shapes/link.js';
3
+ export type { CmsLinkValue } from '../../core/shapes/link.js';
11
4
 
12
5
  export type CmsLinkRenderProps = { label: string; href: string; newTab: boolean };
13
6
  </script>
14
7
 
15
8
  <script lang="ts">
16
9
  import type { Snippet } from 'svelte';
17
- import { getCmsScope } from './scope.js';
18
- import { cmsStore, type CmsScopeRef } from './cms-store.svelte.js';
10
+ import { useCmsField } from './use-cms-field.svelte.js';
19
11
  import { resolveRouteUrl } from '../admin-bar/resolve-route.js';
12
+ import { normalizeLink, type CmsLinkValue } from '../../core/shapes/link.js';
20
13
 
21
14
  type Props = {
22
15
  name: string;
16
+ scope?: string;
23
17
  fallback?: CmsLinkValue;
24
18
  value?: unknown;
25
19
  class?: string;
26
20
  children?: Snippet<[CmsLinkRenderProps]>;
27
21
  };
28
- let { name, fallback, value, class: className, children }: Props = $props();
22
+ let { name, scope, fallback, value, class: className, children }: Props = $props();
29
23
 
30
- const scope = getCmsScope();
31
- const ref = $derived<CmsScopeRef | null>(
32
- scope ? { scopeId: scope.scopeId, routeId: scope.routeId, params: scope.params } : null
24
+ const field = useCmsField(
25
+ () => ({ name, scope, value }),
26
+ (raw): CmsLinkValue => (raw === undefined ? (fallback ?? {}) : normalizeLink(raw))
33
27
  );
34
28
 
35
- const resolved = $derived.by(() => {
36
- if (value !== undefined) return value;
37
- return ref ? cmsStore.getValue(ref, name) : undefined;
38
- });
39
-
40
- const link = $derived.by((): CmsLinkValue => {
41
- if (resolved && typeof resolved === 'object') return resolved as CmsLinkValue;
42
- return fallback ?? {};
43
- });
29
+ const link = $derived(field.current);
44
30
 
45
31
  const href = $derived.by(() => {
46
32
  if (link.routeId) {
@@ -55,12 +41,11 @@
55
41
 
56
42
  const label = $derived(link.label ?? '');
57
43
  const newTab = $derived(!!link.newTab);
58
- const editable = $derived(cmsStore.isEditing && value === undefined && !!ref);
59
44
  </script>
60
45
 
61
- {#if editable && ref}
46
+ {#if field.editable && field.ref}
62
47
  {#await import('./cms-link-editable.svelte') then { default: Editable }}
63
- <Editable scope={ref} {name} initial={link} {className} {children} />
48
+ <Editable scope={field.ref} {name} initial={link} {className} {children} />
64
49
  {/await}
65
50
  {:else if children}
66
51
  {@render children({ label, href, newTab })}
@@ -73,8 +58,10 @@
73
58
  >
74
59
  {label || name}
75
60
  </a>
61
+ {:else if field.raw === null}
62
+ <!-- cleared by the editor -->
76
63
  {:else}
77
- <span class="cms-missing" data-cms-name={name} data-cms-scope={scope?.scopeId ?? '?'}>
64
+ <span class="cms-missing" data-cms-name={name} data-cms-scope={field.ref?.scopeId ?? '?'}>
78
65
  {name}
79
66
  </span>
80
67
  {/if}
@@ -1,20 +1,15 @@
1
- export type CmsLinkValue = {
2
- label?: string;
3
- /** Raw URL — used when `routeId` is absent. */
4
- href?: string;
5
- /** SvelteKit route id, e.g. `/blog/[slug]`. Resolved at render time. */
6
- routeId?: string;
7
- params?: Record<string, string>;
8
- newTab?: boolean;
9
- };
1
+ export { normalizeLink } from '../../core/shapes/link.js';
2
+ export type { CmsLinkValue } from '../../core/shapes/link.js';
10
3
  export type CmsLinkRenderProps = {
11
4
  label: string;
12
5
  href: string;
13
6
  newTab: boolean;
14
7
  };
15
8
  import type { Snippet } from 'svelte';
9
+ import { type CmsLinkValue } from '../../core/shapes/link.js';
16
10
  type Props = {
17
11
  name: string;
12
+ scope?: string;
18
13
  fallback?: CmsLinkValue;
19
14
  value?: unknown;
20
15
  class?: string;
@@ -0,0 +1,29 @@
1
+ <script lang="ts" module>
2
+ /** `CmsLogos` preset: logo image, name, optional link. */
3
+ export type { CmsLogosValue, LogoItem, LogoItemView } from '../../core/shapes/presets.js';
4
+ export { cmsLogos, logosView } from '../../core/shapes/presets.js';
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import type { Snippet } from 'svelte';
9
+ import Structured from './cms-structured.svelte';
10
+ import {
11
+ cmsLogos,
12
+ logosView,
13
+ type CmsLogosValue,
14
+ type LogoItem,
15
+ type LogoItemView
16
+ } from '../../core/shapes/presets.js';
17
+
18
+ type Props = {
19
+ name: string;
20
+ scope?: string;
21
+ /** The full value, or a bare item list. */
22
+ fallback?: Partial<CmsLogosValue> | Partial<LogoItem>[];
23
+ value?: unknown;
24
+ children: Snippet<[LogoItemView[]]>;
25
+ };
26
+ let { name, scope, fallback, value, children }: Props = $props();
27
+ </script>
28
+
29
+ <Structured schema={cmsLogos} {name} {scope} {fallback} {value} view={logosView} {children} />
@@ -0,0 +1,16 @@
1
+ /** `CmsLogos` preset: logo image, name, optional link. */
2
+ export type { CmsLogosValue, LogoItem, LogoItemView } from '../../core/shapes/presets.js';
3
+ export { cmsLogos, logosView } from '../../core/shapes/presets.js';
4
+ import type { Snippet } from 'svelte';
5
+ import { type CmsLogosValue, type LogoItem, type LogoItemView } from '../../core/shapes/presets.js';
6
+ type Props = {
7
+ name: string;
8
+ scope?: string;
9
+ /** The full value, or a bare item list. */
10
+ fallback?: Partial<CmsLogosValue> | Partial<LogoItem>[];
11
+ value?: unknown;
12
+ children: Snippet<[LogoItemView[]]>;
13
+ };
14
+ declare const CmsLogos: import("svelte").Component<Props, {}, "">;
15
+ type CmsLogos = ReturnType<typeof CmsLogos>;
16
+ export default CmsLogos;
@@ -1,42 +1,41 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import { marked } from 'marked';
4
- import { getCmsScope } from './scope.js';
5
- import { cmsStore, type CmsScopeRef } from './cms-store.svelte.js';
4
+ import { useCmsField } from './use-cms-field.svelte.js';
6
5
 
7
6
  type Props = {
8
7
  name: string;
8
+ scope?: string;
9
9
  fallback?: string;
10
10
  value?: unknown;
11
11
  children?: Snippet;
12
12
  };
13
13
 
14
- let { name, fallback, value, children }: Props = $props();
14
+ let { name, scope, fallback, value, children }: Props = $props();
15
15
 
16
- const scope = getCmsScope();
17
- const ref = $derived<CmsScopeRef | null>(
18
- scope ? { scopeId: scope.scopeId, routeId: scope.routeId, params: scope.params } : null
16
+ const field = useCmsField(
17
+ () => ({ name, scope, value }),
18
+ (raw): string | undefined => {
19
+ if (raw === undefined) return fallback;
20
+ return typeof raw === 'string' ? raw : '';
21
+ }
19
22
  );
20
23
 
21
- const resolved = $derived.by(() => {
22
- if (value !== undefined) return value;
23
- return ref ? cmsStore.getValue(ref, name) : undefined;
24
- });
25
-
26
- const source = $derived(typeof resolved === 'string' ? resolved : (fallback ?? ''));
27
- const editable = $derived(cmsStore.isEditing && value === undefined && !!ref);
24
+ const source = $derived(field.current ?? '');
28
25
  </script>
29
26
 
30
- {#if editable && ref}
27
+ {#if field.editable && field.ref}
31
28
  {#await import('./cms-markdown-editable.svelte') then { default: Editable }}
32
- <Editable scope={ref} {name} initial={source} />
29
+ <Editable scope={field.ref} {name} initial={source} />
33
30
  {/await}
34
31
  {:else if source}
35
32
  <div class="cms-markdown">{@html marked.parse(source, { async: false })}</div>
33
+ {:else if field.raw === null}
34
+ <!-- cleared by the editor -->
36
35
  {:else if children}
37
36
  {@render children()}
38
37
  {:else}
39
- <div class="cms-missing" data-cms-name={name} data-cms-scope={scope?.scopeId ?? '?'}>
38
+ <div class="cms-missing" data-cms-name={name} data-cms-scope={field.ref?.scopeId ?? '?'}>
40
39
  {name}
41
40
  </div>
42
41
  {/if}
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  type Props = {
3
3
  name: string;
4
+ scope?: string;
4
5
  fallback?: string;
5
6
  value?: unknown;
6
7
  children?: Snippet;
@@ -0,0 +1,22 @@
1
+ <script lang="ts" module>
2
+ /** `CmsNav`: site navigation. Lives in the root layout; other places read it with `scope="root"`. */
3
+ export * from '../../core/shapes/nav.js';
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import type { Snippet } from 'svelte';
8
+ import Structured from './cms-structured.svelte';
9
+ import { cmsNav, navView, type CmsNavValue, type NavItemView } from '../../core/shapes/nav.js';
10
+
11
+ type Props = {
12
+ name: string;
13
+ scope?: string;
14
+ /** The full value, or a bare item list. */
15
+ fallback?: Partial<CmsNavValue> | Array<Partial<CmsNavValue['items'][number]>>;
16
+ value?: unknown;
17
+ children: Snippet<[NavItemView[]]>;
18
+ };
19
+ let { name, scope, fallback, value, children }: Props = $props();
20
+ </script>
21
+
22
+ <Structured schema={cmsNav} {name} {scope} {fallback} {value} view={navView} {children} />
@@ -0,0 +1,15 @@
1
+ /** `CmsNav`: site navigation. Lives in the root layout; other places read it with `scope="root"`. */
2
+ export * from '../../core/shapes/nav.js';
3
+ import type { Snippet } from 'svelte';
4
+ import { type CmsNavValue, type NavItemView } from '../../core/shapes/nav.js';
5
+ type Props = {
6
+ name: string;
7
+ scope?: string;
8
+ /** The full value, or a bare item list. */
9
+ fallback?: Partial<CmsNavValue> | Array<Partial<CmsNavValue['items'][number]>>;
10
+ value?: unknown;
11
+ children: Snippet<[NavItemView[]]>;
12
+ };
13
+ declare const CmsNav: import("svelte").Component<Props, {}, "">;
14
+ type CmsNav = ReturnType<typeof CmsNav>;
15
+ export default CmsNav;
@@ -15,7 +15,7 @@
15
15
 
16
16
  const live = $derived(cmsStore.getValue(scope, name));
17
17
  const display = $derived.by(() => {
18
- const v = typeof live === 'number' ? live : initial;
18
+ const v = typeof live === 'number' ? live : live === null ? undefined : initial;
19
19
  return typeof v === 'number' && Number.isFinite(v) ? String(v) : '';
20
20
  });
21
21
 
@@ -34,7 +34,9 @@
34
34
 
35
35
  const onInput = (e: Event) => {
36
36
  const raw = (e.currentTarget as HTMLInputElement).value;
37
- if (raw === '') return;
37
+ // An emptied input clears the field explicitly (`null`), so the
38
+ // published value no longer shows through.
39
+ if (raw === '') return cmsStore.setValue(scope, name, null);
38
40
  const n = Number(raw);
39
41
  if (!Number.isFinite(n)) return;
40
42
  cmsStore.setValue(scope, name, integer ? Math.trunc(n) : n);
@@ -3,8 +3,9 @@
3
3
 
4
4
  export type CmsNumberProps = {
5
5
  name: string;
6
+ scope?: string;
6
7
  fallback?: number;
7
- /** Per-item override used by `CmsRepeater`. */
8
+ /** Per-item override used by structured components. */
8
9
  value?: unknown;
9
10
  min?: number;
10
11
  max?: number;
@@ -16,37 +17,37 @@
16
17
  </script>
17
18
 
18
19
  <script lang="ts">
19
- import { getCmsScope } from './scope.js';
20
- import { cmsStore, type CmsScopeRef } from './cms-store.svelte.js';
20
+ import { useCmsField } from './use-cms-field.svelte.js';
21
21
 
22
- let { name, fallback, value, min, max, step, integer, children }: CmsNumberProps = $props();
22
+ let { name, scope, fallback, value, min, max, step, integer, children }: CmsNumberProps =
23
+ $props();
23
24
 
24
- const scope = getCmsScope();
25
- const ref = $derived<CmsScopeRef | null>(
26
- scope ? { scopeId: scope.scopeId, routeId: scope.routeId, params: scope.params } : null
25
+ const field = useCmsField(
26
+ () => ({ name, scope, value }),
27
+ (raw): number | undefined => {
28
+ if (raw === undefined) return fallback;
29
+ return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;
30
+ }
27
31
  );
28
32
 
29
- const resolved = $derived.by(() => {
30
- if (value !== undefined) return value;
31
- return ref ? cmsStore.getValue(ref, name) : undefined;
32
- });
33
-
34
- const current = $derived(typeof resolved === 'number' ? resolved : fallback);
35
- const editable = $derived(cmsStore.isEditing && value === undefined && !!ref);
33
+ const current = $derived(field.current);
36
34
  </script>
37
35
 
38
- {#if editable && ref}
36
+ {#if field.editable && field.ref}
39
37
  {#await import('./cms-number-editable.svelte') then { default: Editable }}
40
- <Editable scope={ref} {name} initial={current} {min} {max} {step} {integer} />
38
+ <Editable scope={field.ref} {name} initial={current} {min} {max} {step} {integer} />
41
39
  {/await}
42
40
  {:else if current !== undefined && children}
43
41
  {@render children(current)}
44
42
  {:else if current !== undefined}
45
43
  {current}
44
+ {:else if field.raw === null}
45
+ <!-- cleared by the editor -->
46
46
  {:else if children}
47
47
  {@render children(0)}
48
48
  {:else}
49
- <span class="cms-missing" data-cms-name={name} data-cms-scope={scope?.scopeId ?? '?'}>{name}</span
49
+ <span class="cms-missing" data-cms-name={name} data-cms-scope={field.ref?.scopeId ?? '?'}
50
+ >{name}</span
50
51
  >
51
52
  {/if}
52
53
 
@@ -1,8 +1,9 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  export type CmsNumberProps = {
3
3
  name: string;
4
+ scope?: string;
4
5
  fallback?: number;
5
- /** Per-item override used by `CmsRepeater`. */
6
+ /** Per-item override used by structured components. */
6
7
  value?: unknown;
7
8
  min?: number;
8
9
  max?: number;
@@ -0,0 +1,36 @@
1
+ <script lang="ts" module>
2
+ /** `CmsPricing`: tiers with a feature checklist, formatted in the visitor's locale. */
3
+ export * from '../../core/shapes/pricing.js';
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import type { Snippet } from 'svelte';
8
+ import Structured from './cms-structured.svelte';
9
+ import { cmsStore } from './cms-store.svelte.js';
10
+ import {
11
+ cmsPricing,
12
+ pricingView,
13
+ type CmsPricingValue,
14
+ type PricingTierView
15
+ } from '../../core/shapes/pricing.js';
16
+
17
+ type Props = {
18
+ name: string;
19
+ scope?: string;
20
+ fallback?: CmsPricingValue;
21
+ value?: unknown;
22
+ children: Snippet<[PricingTierView[]]>;
23
+ };
24
+ let { name, scope, fallback, value, children }: Props = $props();
25
+ const locale = $derived(cmsStore.activeLocale || 'en');
26
+ </script>
27
+
28
+ <Structured
29
+ schema={cmsPricing}
30
+ {name}
31
+ {scope}
32
+ {fallback}
33
+ {value}
34
+ view={(v: CmsPricingValue) => pricingView(v, locale)}
35
+ {children}
36
+ />
@@ -0,0 +1,14 @@
1
+ /** `CmsPricing`: tiers with a feature checklist, formatted in the visitor's locale. */
2
+ export * from '../../core/shapes/pricing.js';
3
+ import type { Snippet } from 'svelte';
4
+ import { type CmsPricingValue, type PricingTierView } from '../../core/shapes/pricing.js';
5
+ type Props = {
6
+ name: string;
7
+ scope?: string;
8
+ fallback?: CmsPricingValue;
9
+ value?: unknown;
10
+ children: Snippet<[PricingTierView[]]>;
11
+ };
12
+ declare const CmsPricing: import("svelte").Component<Props, {}, "">;
13
+ type CmsPricing = ReturnType<typeof CmsPricing>;
14
+ export default CmsPricing;
@@ -1,48 +1,41 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { getCmsScope } from './scope.js';
4
- import { cmsStore, type CmsScopeRef } from './cms-store.svelte.js';
3
+ import { useCmsField } from './use-cms-field.svelte.js';
5
4
 
6
5
  type Props = {
7
6
  name: string;
7
+ scope?: string;
8
8
  fallback?: string;
9
9
  value?: unknown;
10
10
  children?: Snippet;
11
11
  };
12
12
 
13
- let { name, fallback, value, children }: Props = $props();
13
+ let { name, scope, fallback, value, children }: Props = $props();
14
14
 
15
- const scope = getCmsScope();
16
- const ref = $derived<CmsScopeRef | null>(
17
- scope ? { scopeId: scope.scopeId, routeId: scope.routeId, params: scope.params } : null
15
+ const field = useCmsField(
16
+ () => ({ name, scope, value }),
17
+ (raw): string | undefined => {
18
+ if (raw === undefined) return fallback;
19
+ return typeof raw === 'string' ? raw : '';
20
+ }
18
21
  );
19
22
 
20
- const resolved = $derived.by(() => {
21
- if (value !== undefined) return value;
22
- return ref ? cmsStore.getValue(ref, name) : undefined;
23
- });
24
-
25
- const html = $derived(typeof resolved === 'string' ? resolved : (fallback ?? ''));
26
- const editable = $derived(cmsStore.isEditing && value === undefined && !!ref);
23
+ const html = $derived(field.current ?? '');
27
24
 
28
25
  let childrenEl = $state<HTMLDivElement>();
29
26
  let initialFromChildren = $state<string | undefined>(undefined);
30
27
 
31
28
  $effect.pre(() => {
32
- if (editable && childrenEl && initialFromChildren === undefined) {
29
+ if (field.editable && childrenEl && initialFromChildren === undefined) {
33
30
  const inner = childrenEl.innerHTML.trim();
34
31
  initialFromChildren = inner || undefined;
35
32
  }
36
33
  });
37
34
 
38
- const editInitial = $derived.by(() => {
39
- if (typeof resolved === 'string') return resolved;
40
- if (fallback !== undefined) return fallback;
41
- return initialFromChildren ?? '';
42
- });
35
+ const editInitial = $derived(field.current ?? initialFromChildren ?? '');
43
36
  </script>
44
37
 
45
- {#if editable && ref}
38
+ {#if field.editable && field.ref}
46
39
  <!-- Mounted straight into edit mode (navigation while editing): the
47
40
  children branch below never rendered, so render it hidden once to
48
41
  capture the default markup. Removed as soon as it's captured. -->
@@ -50,14 +43,16 @@
50
43
  <div bind:this={childrenEl} class="cms-rich-text-capture">{@render children()}</div>
51
44
  {/if}
52
45
  {#await import('./cms-rich-text-editable.svelte') then { default: Editable }}
53
- <Editable scope={ref} {name} initial={editInitial} />
46
+ <Editable scope={field.ref} {name} initial={editInitial} />
54
47
  {/await}
55
48
  {:else if html}
56
49
  <div class="cms-rich-text">{@html html}</div>
50
+ {:else if field.raw === null}
51
+ <!-- cleared by the editor -->
57
52
  {:else if children}
58
53
  <div bind:this={childrenEl} class="cms-rich-text-children">{@render children()}</div>
59
54
  {:else}
60
- <div class="cms-missing" data-cms-name={name} data-cms-scope={scope?.scopeId ?? '?'}>
55
+ <div class="cms-missing" data-cms-name={name} data-cms-scope={field.ref?.scopeId ?? '?'}>
61
56
  {name}
62
57
  </div>
63
58
  {/if}
@@ -1,6 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  type Props = {
3
3
  name: string;
4
+ scope?: string;
4
5
  fallback?: string;
5
6
  value?: unknown;
6
7
  children?: Snippet;
@@ -0,0 +1,35 @@
1
+ <script lang="ts" module>
2
+ /** `CmsSchedule` preset: day, time, title, body, location. `scheduleByDay` groups them. */
3
+ export type { CmsScheduleValue, ScheduleItem } from '../../core/shapes/presets.js';
4
+ export { cmsSchedule, scheduleByDay } from '../../core/shapes/presets.js';
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import type { Snippet } from 'svelte';
9
+ import Structured from './cms-structured.svelte';
10
+ import {
11
+ cmsSchedule,
12
+ type CmsScheduleValue,
13
+ type ScheduleItem
14
+ } from '../../core/shapes/presets.js';
15
+
16
+ type Props = {
17
+ name: string;
18
+ scope?: string;
19
+ /** The full value, or a bare item list. */
20
+ fallback?: Partial<CmsScheduleValue> | Partial<ScheduleItem>[];
21
+ value?: unknown;
22
+ children: Snippet<[ScheduleItem[]]>;
23
+ };
24
+ let { name, scope, fallback, value, children }: Props = $props();
25
+ </script>
26
+
27
+ <Structured
28
+ schema={cmsSchedule}
29
+ {name}
30
+ {scope}
31
+ {fallback}
32
+ {value}
33
+ view={(v: CmsScheduleValue) => v.items}
34
+ {children}
35
+ />
@@ -0,0 +1,16 @@
1
+ /** `CmsSchedule` preset: day, time, title, body, location. `scheduleByDay` groups them. */
2
+ export type { CmsScheduleValue, ScheduleItem } from '../../core/shapes/presets.js';
3
+ export { cmsSchedule, scheduleByDay } from '../../core/shapes/presets.js';
4
+ import type { Snippet } from 'svelte';
5
+ import { type CmsScheduleValue, type ScheduleItem } from '../../core/shapes/presets.js';
6
+ type Props = {
7
+ name: string;
8
+ scope?: string;
9
+ /** The full value, or a bare item list. */
10
+ fallback?: Partial<CmsScheduleValue> | Partial<ScheduleItem>[];
11
+ value?: unknown;
12
+ children: Snippet<[ScheduleItem[]]>;
13
+ };
14
+ declare const CmsSchedule: import("svelte").Component<Props, {}, "">;
15
+ type CmsSchedule = ReturnType<typeof CmsSchedule>;
16
+ export default CmsSchedule;
@@ -0,0 +1,35 @@
1
+ <script lang="ts" module>
2
+ /** `CmsSocialLinks`: profile links; the template supplies the icons. */
3
+ export * from '../../core/shapes/social-links.js';
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import type { Snippet } from 'svelte';
8
+ import Structured from './cms-structured.svelte';
9
+ import {
10
+ cmsSocialLinks,
11
+ socialLinksView,
12
+ type CmsSocialLinksValue,
13
+ type SocialLinkView
14
+ } from '../../core/shapes/social-links.js';
15
+
16
+ type Props = {
17
+ name: string;
18
+ scope?: string;
19
+ /** The full value, or a bare item list. */
20
+ fallback?: Partial<CmsSocialLinksValue> | Array<Partial<CmsSocialLinksValue['items'][number]>>;
21
+ value?: unknown;
22
+ children: Snippet<[SocialLinkView[]]>;
23
+ };
24
+ let { name, scope, fallback, value, children }: Props = $props();
25
+ </script>
26
+
27
+ <Structured
28
+ schema={cmsSocialLinks}
29
+ {name}
30
+ {scope}
31
+ {fallback}
32
+ {value}
33
+ view={socialLinksView}
34
+ {children}
35
+ />
@@ -0,0 +1,15 @@
1
+ /** `CmsSocialLinks`: profile links; the template supplies the icons. */
2
+ export * from '../../core/shapes/social-links.js';
3
+ import type { Snippet } from 'svelte';
4
+ import { type CmsSocialLinksValue, type SocialLinkView } from '../../core/shapes/social-links.js';
5
+ type Props = {
6
+ name: string;
7
+ scope?: string;
8
+ /** The full value, or a bare item list. */
9
+ fallback?: Partial<CmsSocialLinksValue> | Array<Partial<CmsSocialLinksValue['items'][number]>>;
10
+ value?: unknown;
11
+ children: Snippet<[SocialLinkView[]]>;
12
+ };
13
+ declare const CmsSocialLinks: import("svelte").Component<Props, {}, "">;
14
+ type CmsSocialLinks = ReturnType<typeof CmsSocialLinks>;
15
+ export default CmsSocialLinks;
@@ -0,0 +1,31 @@
1
+ <script lang="ts" module>
2
+ /** `CmsStats` preset: value, label, note. */
3
+ export type { CmsStatsValue, StatItem } from '../../core/shapes/presets.js';
4
+ export { cmsStats } from '../../core/shapes/presets.js';
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import type { Snippet } from 'svelte';
9
+ import Structured from './cms-structured.svelte';
10
+ import { cmsStats, type CmsStatsValue, type StatItem } from '../../core/shapes/presets.js';
11
+
12
+ type Props = {
13
+ name: string;
14
+ scope?: string;
15
+ /** The full value, or a bare item list. */
16
+ fallback?: Partial<CmsStatsValue> | Partial<StatItem>[];
17
+ value?: unknown;
18
+ children: Snippet<[StatItem[]]>;
19
+ };
20
+ let { name, scope, fallback, value, children }: Props = $props();
21
+ </script>
22
+
23
+ <Structured
24
+ schema={cmsStats}
25
+ {name}
26
+ {scope}
27
+ {fallback}
28
+ {value}
29
+ view={(v: CmsStatsValue) => v.items}
30
+ {children}
31
+ />
@@ -0,0 +1,16 @@
1
+ /** `CmsStats` preset: value, label, note. */
2
+ export type { CmsStatsValue, StatItem } from '../../core/shapes/presets.js';
3
+ export { cmsStats } from '../../core/shapes/presets.js';
4
+ import type { Snippet } from 'svelte';
5
+ import { type CmsStatsValue, type StatItem } from '../../core/shapes/presets.js';
6
+ type Props = {
7
+ name: string;
8
+ scope?: string;
9
+ /** The full value, or a bare item list. */
10
+ fallback?: Partial<CmsStatsValue> | Partial<StatItem>[];
11
+ value?: unknown;
12
+ children: Snippet<[StatItem[]]>;
13
+ };
14
+ declare const CmsStats: import("svelte").Component<Props, {}, "">;
15
+ type CmsStats = ReturnType<typeof CmsStats>;
16
+ export default CmsStats;