@vit-foundation/ui 0.15.0 → 0.16.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 (40) hide show
  1. package/README.md +10 -10
  2. package/dist/components/jobs/JobList.svelte +11 -19
  3. package/dist/components/jobs/JobList.svelte.d.ts +1 -1
  4. package/dist/components/layout/Footer.svelte +9 -15
  5. package/dist/components/layout/Nav.svelte +10 -15
  6. package/dist/components/projects/ProjectCard.svelte +2 -2
  7. package/dist/components/projects/ProjectCard.svelte.d.ts +1 -1
  8. package/dist/components/team/CollaboratorList.svelte +10 -16
  9. package/dist/components/timeline/Timeline.svelte +10 -23
  10. package/dist/components/timeline/TimelineMilestone.svelte +2 -2
  11. package/dist/components/timeline/TimelineMilestone.svelte.d.ts +1 -1
  12. package/dist/components/weeklies/WeeklieCard.svelte +2 -2
  13. package/dist/components/weeklies/WeeklieCard.svelte.d.ts +1 -1
  14. package/dist/config/index.d.ts +1 -1
  15. package/dist/config/messages.js +5 -1
  16. package/dist/config/types.d.ts +35 -3
  17. package/dist/content-components.d.ts +5 -1
  18. package/dist/content-components.js +4 -1
  19. package/dist/edit/chrome/EditPanel.svelte +2 -2
  20. package/dist/edit/chrome/EditPanel.svelte.d.ts +2 -2
  21. package/dist/edit/chrome/PropertyRow.svelte +40 -9
  22. package/dist/edit/chrome/PropertyRow.svelte.d.ts +6 -2
  23. package/dist/edit/collection.svelte.d.ts +49 -0
  24. package/dist/edit/collection.svelte.js +39 -0
  25. package/dist/edit/helpers.d.ts +5 -5
  26. package/dist/edit/helpers.js +2 -2
  27. package/dist/edit/index.d.ts +4 -1
  28. package/dist/edit/index.js +1 -0
  29. package/dist/edit/types.d.ts +24 -9
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +2 -2
  32. package/dist/utils/milestones.d.ts +15 -0
  33. package/dist/utils/milestones.js +17 -0
  34. package/dist/utils/paths.d.ts +6 -0
  35. package/dist/utils/paths.js +14 -0
  36. package/dist/utils/url-filters.svelte.d.ts +58 -0
  37. package/dist/utils/url-filters.svelte.js +47 -0
  38. package/dist/utils/weekly-list.svelte.d.ts +82 -0
  39. package/dist/utils/weekly-list.svelte.js +120 -0
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -22,16 +22,16 @@ Requires `svelte` ^5.0.0 as a peer. Import the two stylesheets once:
22
22
 
23
23
  Everything exports flat from the root, and again grouped by role:
24
24
 
25
- | Entry point | What lives there |
26
- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
- | [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FilterChips, Pagination, RichText, SearchInput, ShareRow |
28
- | [`/forms`](./docs/forms.md) | Field, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds |
29
- | [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer |
30
- | [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect — plus the data shapes and helpers |
31
- | [`/community`](./docs/components/community.md) | AuthPageShell, LoginForm, SignupForm, GoogleAuthForm, AccountPanel, NewsletterSignup, CommentSection, ReactionBar, ContactForm |
32
- | [`/edit`](./docs/edit-mode.md) | Editable, setEditAdapter/getEditAdapter, descriptors and helpers, LocalizedText |
33
- | [`/config`](./docs/getting-started.md#wiring-an-app-uiprovider) | UiProvider, UiConfig, the locale set, the default Catalan messages |
34
- | `/testing` | createRemoteFormMock — the remote-form stand-in for stories and tests |
25
+ | Entry point | What lives there |
26
+ | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | [`/primitives`](./docs/components/primitives.md) | Button, GhostButton, Link, CardMedia, CardTitle, CopyIntro, DateText, DecorShapes, FilterChips, Pagination, RichText, SearchInput, ShareRow |
28
+ | [`/forms`](./docs/forms.md) | Field, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds |
29
+ | [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer |
30
+ | [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect — plus the data shapes, helpers and the two list rules (createWeeklyList, createUrlFilters) |
31
+ | [`/community`](./docs/components/community.md) | AuthPageShell, LoginForm, SignupForm, GoogleAuthForm, AccountPanel, NewsletterSignup, CommentSection, ReactionBar, ContactForm |
32
+ | [`/edit`](./docs/edit-mode.md) | Editable, setEditAdapter/getEditAdapter, descriptors and helpers, collectionEditing, LocalizedText |
33
+ | [`/config`](./docs/getting-started.md#wiring-an-app-uiprovider) | UiProvider, UiConfig, the locale set, the default Catalan messages |
34
+ | `/testing` | createRemoteFormMock — the remote-form stand-in for stories and tests |
35
35
 
36
36
  Every component renders standalone (Catalan copy, identity hrefs); an app
37
37
  integrates its i18n and router through one `UiProvider` in the root layout.
@@ -9,7 +9,7 @@
9
9
  title?: EditDescriptor;
10
10
  description?: EditDescriptor;
11
11
  postedOn?: PropertyDescriptor;
12
- /** Editorial state row; value derives from `draft` ('false' when draft). */
12
+ /** Editorial-state `flag` row; on while the opening is not a draft. */
13
13
  status?: PropertyDescriptor;
14
14
  /** Accessible name for the frame — usually the opening's title. */
15
15
  label?: string;
@@ -25,7 +25,7 @@
25
25
  import DraftBadge from '../../edit/chrome/DraftBadge.svelte';
26
26
  import EditFrame from '../../edit/chrome/EditFrame.svelte';
27
27
  import EditPanel from '../../edit/chrome/EditPanel.svelte';
28
- import { getEditAdapter } from '../../edit/context.js';
28
+ import { collectionEditing } from '../../edit/collection.svelte.js';
29
29
  import Editable from '../../edit/Editable.svelte';
30
30
  import type { CollectionRef } from '../../edit/types.js';
31
31
  import DateText from '../ui/DateText.svelte';
@@ -48,27 +48,19 @@
48
48
  let { jobs, editFor, collection }: Props = $props();
49
49
 
50
50
  const config = getUiConfig();
51
- const adapter = getEditAdapter();
52
51
  const msg = $derived(config.messages);
53
52
 
54
- const structural = $derived(
55
- collection !== undefined && (adapter?.isEditing ?? false) && adapter?.applyOp !== undefined
56
- );
57
-
58
- /** The map the host gave, plus removal — the list owns identity. */
59
- function editMapFor(job: JobOpeningData): JobEditMap | undefined {
60
- const map = editFor?.(job);
61
- if (!structural || !collection || job.id === undefined) return map;
62
- return { ...map, removeOp: { kind: 'remove', collection, id: job.id } };
63
- }
53
+ // The structural half (add slot, removal for rows with an id) — see
54
+ // collectionEditing.
55
+ const list = collectionEditing<JobOpeningData, JobEditMap>(() => ({ collection, editFor }));
64
56
  </script>
65
57
 
66
58
  {#if jobs.length === 0}
67
59
  <Editable edit={config.messageEdit?.('jobs_empty')} value={msg.jobs_empty()}>
68
60
  {#snippet children(text, attrs)}<p class="empty" {...attrs}>{text}</p>{/snippet}
69
61
  </Editable>
70
- {#if structural && collection}
71
- <AddSlot op={{ kind: 'create', collection }} />
62
+ {#if list.add}
63
+ <AddSlot op={list.add} />
72
64
  {/if}
73
65
  <Editable edit={config.messageEdit?.('jobs_newsletterNudge')} value={msg.jobs_newsletterNudge()}>
74
66
  {#snippet children(text, attrs)}<p class="nudge" {...attrs}>{text}</p>{/snippet}
@@ -76,10 +68,10 @@
76
68
  {:else}
77
69
  <ul>
78
70
  {#each jobs as job (job.slug)}
79
- {@const edit = editMapFor(job)}
71
+ {@const edit = list.mapFor(job)}
80
72
  {@const rows = [
81
73
  edit?.postedOn && { descriptor: edit.postedOn, value: job.postedOn },
82
- edit?.status && { descriptor: edit.status, value: job.draft ? 'false' : 'true' }
74
+ edit?.status && { descriptor: edit.status, value: !job.draft }
83
75
  ].filter((row) => row !== undefined)}
84
76
  <li>
85
77
  <!-- Inside the li — see CollaboratorList. -->
@@ -108,9 +100,9 @@
108
100
  </EditFrame>
109
101
  </li>
110
102
  {/each}
111
- {#if structural && collection}
103
+ {#if list.add}
112
104
  <li class="add-slot">
113
- <AddSlot op={{ kind: 'create', collection }} />
105
+ <AddSlot op={list.add} />
114
106
  </li>
115
107
  {/if}
116
108
  </ul>
@@ -7,7 +7,7 @@ export interface JobEditMap {
7
7
  title?: EditDescriptor;
8
8
  description?: EditDescriptor;
9
9
  postedOn?: PropertyDescriptor;
10
- /** Editorial state row; value derives from `draft` ('false' when draft). */
10
+ /** Editorial-state `flag` row; on while the opening is not a draft. */
11
11
  status?: PropertyDescriptor;
12
12
  /** Accessible name for the frame — usually the opening's title. */
13
13
  label?: string;
@@ -3,7 +3,7 @@
3
3
  import type { SiteLink } from '../../config/types.js';
4
4
  import AddSlot from '../../edit/chrome/AddSlot.svelte';
5
5
  import LinkEdit from '../../edit/chrome/LinkEdit.svelte';
6
- import { getEditAdapter } from '../../edit/context.js';
6
+ import { collectionEditing } from '../../edit/collection.svelte.js';
7
7
  import Editable from '../../edit/Editable.svelte';
8
8
  import type { CollectionRef, EditDescriptor } from '../../edit/types.js';
9
9
  import Link from '../ui/Link.svelte';
@@ -27,18 +27,12 @@
27
27
  let { links, editFor = undefined, propertiesFor = undefined, collection }: Props = $props();
28
28
 
29
29
  const config = getUiConfig();
30
- const adapter = getEditAdapter();
31
30
 
32
- const structural = $derived(
33
- collection !== undefined && (adapter?.isEditing ?? false) && adapter?.applyOp !== undefined
34
- );
35
-
36
- /** The map the host gave, plus removal — the list owns identity. */
37
- function editMapFor(link: SiteLink): SiteLinkEditMap | undefined {
38
- const map = propertiesFor?.(link);
39
- if (!structural || !collection || link.id === undefined) return map;
40
- return { ...map, removeOp: { kind: 'remove', collection, id: link.id } };
41
- }
31
+ // Same structural half as Nav — see collectionEditing.
32
+ const list = collectionEditing<SiteLink, SiteLinkEditMap>(() => ({
33
+ collection,
34
+ editFor: propertiesFor
35
+ }));
42
36
 
43
37
  const year = new Date().getFullYear();
44
38
  </script>
@@ -50,7 +44,7 @@
50
44
  </span>
51
45
  <ul>
52
46
  {#each links as link (link.href)}
53
- {@const map = editMapFor(link)}
47
+ {@const map = list.mapFor(link)}
54
48
  <li>
55
49
  <!-- One gesture: the modal edits text, destination, order, removal. -->
56
50
  <LinkEdit
@@ -66,9 +60,9 @@
66
60
  </LinkEdit>
67
61
  </li>
68
62
  {/each}
69
- {#if structural && collection}
63
+ {#if list.add}
70
64
  <li>
71
- <AddSlot op={{ kind: 'create', collection }} />
65
+ <AddSlot op={list.add} />
72
66
  </li>
73
67
  {/if}
74
68
  </ul>
@@ -3,7 +3,7 @@
3
3
  import type { SiteLink } from '../../config/types.js';
4
4
  import AddSlot from '../../edit/chrome/AddSlot.svelte';
5
5
  import LinkEdit from '../../edit/chrome/LinkEdit.svelte';
6
- import { getEditAdapter } from '../../edit/context.js';
6
+ import { collectionEditing } from '../../edit/collection.svelte.js';
7
7
  import type { CollectionRef, EditDescriptor } from '../../edit/types.js';
8
8
  import { isPathUnder } from '../../utils/paths.js';
9
9
  import GhostButton from '../ui/GhostButton.svelte';
@@ -45,18 +45,13 @@
45
45
  }: Props = $props();
46
46
 
47
47
  const config = getUiConfig();
48
- const adapter = getEditAdapter();
49
48
 
50
- const structural = $derived(
51
- collection !== undefined && (adapter?.isEditing ?? false) && adapter?.applyOp !== undefined
52
- );
53
-
54
- /** The map the host gave, plus removal — the list owns identity. */
55
- function editMapFor(link: SiteLink): SiteLinkEditMap | undefined {
56
- const map = propertiesFor?.(link);
57
- if (!structural || !collection || link.id === undefined) return map;
58
- return { ...map, removeOp: { kind: 'remove', collection, id: link.id } };
59
- }
49
+ // The structural half — add slot and per-link removal — shared with every
50
+ // other editable list; see collectionEditing.
51
+ const list = collectionEditing<SiteLink, SiteLinkEditMap>(() => ({
52
+ collection,
53
+ editFor: propertiesFor
54
+ }));
60
55
 
61
56
  const msg = $derived(config.messages);
62
57
 
@@ -113,7 +108,7 @@
113
108
  <div class="menu" id="site-menu" class:open={isMenuOpen}>
114
109
  <ul class="links">
115
110
  {#each links as link (link.href)}
116
- {@const map = editMapFor(link)}
111
+ {@const map = list.mapFor(link)}
117
112
  <li>
118
113
  <!-- One gesture: clicking the entry opens the modal that edits
119
114
  its text, destination, order and removal together. -->
@@ -134,9 +129,9 @@
134
129
  </LinkEdit>
135
130
  </li>
136
131
  {/each}
137
- {#if structural && collection}
132
+ {#if list.add}
138
133
  <li>
139
- <AddSlot op={{ kind: 'create', collection }} />
134
+ <AddSlot op={list.add} />
140
135
  </li>
141
136
  {/if}
142
137
  </ul>
@@ -12,7 +12,7 @@
12
12
  publishedOn?: PropertyDescriptor;
13
13
  externalUrl?: PropertyDescriptor;
14
14
  image?: PropertyDescriptor;
15
- /** Editorial state row; value derives from `draft` ('false' when draft). */
15
+ /** Editorial-state `flag` row; on while the project is not a draft. */
16
16
  status?: PropertyDescriptor;
17
17
  /** Accessible name for the frame, e.g. the project's title. */
18
18
  label?: string;
@@ -70,7 +70,7 @@
70
70
  edit?.publishedOn && { descriptor: edit.publishedOn, value: project.publishedOn },
71
71
  edit?.externalUrl && { descriptor: edit.externalUrl, value: project.externalUrl },
72
72
  edit?.image && { descriptor: edit.image, value: project.imageUrl },
73
- edit?.status && { descriptor: edit.status, value: project.draft ? 'false' : 'true' }
73
+ edit?.status && { descriptor: edit.status, value: !project.draft }
74
74
  ].filter((row) => row !== undefined)
75
75
  );
76
76
  const frameSpec = $derived(
@@ -10,7 +10,7 @@ export interface ProjectEditMap {
10
10
  publishedOn?: PropertyDescriptor;
11
11
  externalUrl?: PropertyDescriptor;
12
12
  image?: PropertyDescriptor;
13
- /** Editorial state row; value derives from `draft` ('false' when draft). */
13
+ /** Editorial-state `flag` row; on while the project is not a draft. */
14
14
  status?: PropertyDescriptor;
15
15
  /** Accessible name for the frame, e.g. the project's title. */
16
16
  label?: string;
@@ -22,7 +22,7 @@
22
22
  import AddSlot from '../../edit/chrome/AddSlot.svelte';
23
23
  import EditFrame from '../../edit/chrome/EditFrame.svelte';
24
24
  import EditPanel from '../../edit/chrome/EditPanel.svelte';
25
- import { getEditAdapter } from '../../edit/context.js';
25
+ import { collectionEditing } from '../../edit/collection.svelte.js';
26
26
  import type { CollectionRef } from '../../edit/types.js';
27
27
 
28
28
  interface Props {
@@ -39,18 +39,12 @@
39
39
 
40
40
  let { collaborators, editFor, collection }: Props = $props();
41
41
 
42
- const adapter = getEditAdapter();
43
-
44
- const structural = $derived(
45
- collection !== undefined && (adapter?.isEditing ?? false) && adapter?.applyOp !== undefined
46
- );
47
-
48
- /** The map the host gave, plus removal — the list owns identity. */
49
- function editMapFor(collaborator: CollaboratorData): CollaboratorEditMap | undefined {
50
- const map = editFor?.(collaborator);
51
- if (!structural || !collection || collaborator.id === undefined) return map;
52
- return { ...map, removeOp: { kind: 'remove', collection, id: collaborator.id } };
53
- }
42
+ // The structural half (add slot, removal for rows with an id) — see
43
+ // collectionEditing.
44
+ const list = collectionEditing<CollaboratorData, CollaboratorEditMap>(() => ({
45
+ collection,
46
+ editFor
47
+ }));
54
48
 
55
49
  function rowsFor(collaborator: CollaboratorData, map: CollaboratorEditMap | undefined) {
56
50
  return [
@@ -63,7 +57,7 @@
63
57
 
64
58
  <ul>
65
59
  {#each collaborators as collaborator (collaborator.id ?? collaborator.personName + collaborator.affiliation)}
66
- {@const map = editMapFor(collaborator)}
60
+ {@const map = list.mapFor(collaborator)}
67
61
  {@const rows = rowsFor(collaborator, map)}
68
62
  <li>
69
63
  <!-- Inside the li, so the list's own layout never gains a child. -->
@@ -89,9 +83,9 @@
89
83
  </EditFrame>
90
84
  </li>
91
85
  {/each}
92
- {#if structural && collection}
86
+ {#if list.add}
93
87
  <li class="add-slot">
94
- <AddSlot op={{ kind: 'create', collection }} />
88
+ <AddSlot op={list.add} />
95
89
  </li>
96
90
  {/if}
97
91
  </ul>
@@ -3,7 +3,7 @@
3
3
  import type { MilestoneData } from '../../content/types.js';
4
4
  import type { MilestoneEditMap } from './TimelineMilestone.svelte';
5
5
  import AddSlot from '../../edit/chrome/AddSlot.svelte';
6
- import { getEditAdapter } from '../../edit/context.js';
6
+ import { collectionEditing } from '../../edit/collection.svelte.js';
7
7
  import type { CollectionRef } from '../../edit/types.js';
8
8
  import { yearOf } from '../../utils/dates.js';
9
9
  import TimelineMilestone from './TimelineMilestone.svelte';
@@ -26,18 +26,10 @@
26
26
  let { milestones, variant = 'compact', editFor, collection }: Props = $props();
27
27
 
28
28
  const config = getUiConfig();
29
- const adapter = getEditAdapter();
30
29
 
31
- const structural = $derived(
32
- collection !== undefined && (adapter?.isEditing ?? false) && adapter?.applyOp !== undefined
33
- );
34
-
35
- /** The map the host gave, plus removal — the list owns identity and order. */
36
- function editMapFor(milestone: MilestoneData): MilestoneEditMap | undefined {
37
- const map = editFor?.(milestone);
38
- if (!structural || !collection) return map;
39
- return { ...map, removeOp: { kind: 'remove', collection, id: milestone.id } };
40
- }
30
+ // The structural half (add slots, per-card removal); the list owns
31
+ // identity and order — see collectionEditing.
32
+ const list = collectionEditing<MilestoneData, MilestoneEditMap>(() => ({ collection, editFor }));
41
33
 
42
34
  interface TimelineEntry {
43
35
  milestone: MilestoneData;
@@ -68,27 +60,22 @@
68
60
  >
69
61
  <ol>
70
62
  {#each entries as entry (entry.milestone.id)}
71
- {#if structural && collection}
63
+ {@const before = list.addBefore(entry.milestone.id)}
64
+ {#if before}
72
65
  <li class="add-slot">
73
- <AddSlot
74
- op={{
75
- kind: 'create',
76
- collection,
77
- anchor: { id: entry.milestone.id, placement: 'before' }
78
- }}
79
- />
66
+ <AddSlot op={before} />
80
67
  </li>
81
68
  {/if}
82
69
  <li>
83
70
  {#if entry.yearMarker}
84
71
  <span class="year">{entry.yearMarker}</span>
85
72
  {/if}
86
- <TimelineMilestone milestone={entry.milestone} edit={editMapFor(entry.milestone)} />
73
+ <TimelineMilestone milestone={entry.milestone} edit={list.mapFor(entry.milestone)} />
87
74
  </li>
88
75
  {/each}
89
- {#if structural && collection}
76
+ {#if list.add}
90
77
  <li class="add-slot">
91
- <AddSlot op={{ kind: 'create', collection }} />
78
+ <AddSlot op={list.add} />
92
79
  </li>
93
80
  {/if}
94
81
  </ol>
@@ -13,7 +13,7 @@
13
13
  category?: PropertyDescriptor;
14
14
  linkUrl?: PropertyDescriptor;
15
15
  image?: PropertyDescriptor;
16
- /** Editorial state row; value derives from `draft` ('false' when draft). */
16
+ /** Editorial-state `flag` row; on while the milestone is not a draft. */
17
17
  status?: PropertyDescriptor;
18
18
  /** Accessible name for the frame, e.g. "Fita: Neix la fundació". */
19
19
  label?: string;
@@ -67,7 +67,7 @@
67
67
  },
68
68
  edit?.linkUrl && { descriptor: edit.linkUrl, value: milestone.linkUrl },
69
69
  edit?.image && { descriptor: edit.image, value: milestone.imageUrls[0] ?? null },
70
- edit?.status && { descriptor: edit.status, value: milestone.draft ? 'false' : 'true' }
70
+ edit?.status && { descriptor: edit.status, value: !milestone.draft }
71
71
  ].filter((row) => row !== undefined)
72
72
  );
73
73
 
@@ -11,7 +11,7 @@ export interface MilestoneEditMap {
11
11
  category?: PropertyDescriptor;
12
12
  linkUrl?: PropertyDescriptor;
13
13
  image?: PropertyDescriptor;
14
- /** Editorial state row; value derives from `draft` ('false' when draft). */
14
+ /** Editorial-state `flag` row; on while the milestone is not a draft. */
15
15
  status?: PropertyDescriptor;
16
16
  /** Accessible name for the frame, e.g. "Fita: Neix la fundació". */
17
17
  label?: string;
@@ -10,7 +10,7 @@
10
10
  title?: EditDescriptor;
11
11
  excerpt?: EditDescriptor;
12
12
  image?: PropertyDescriptor;
13
- /** Editorial state row; value derives from `draft` ('false' when draft). */
13
+ /** Editorial-state `flag` row; on while the weekly is not a draft. */
14
14
  status?: PropertyDescriptor;
15
15
  /** Accessible name for the frame, e.g. "Weekly #12". */
16
16
  label?: string;
@@ -49,7 +49,7 @@
49
49
  const panelRows = $derived(
50
50
  [
51
51
  edit?.image && { descriptor: edit.image, value: weekly.imageUrl },
52
- edit?.status && { descriptor: edit.status, value: weekly.draft ? 'false' : 'true' }
52
+ edit?.status && { descriptor: edit.status, value: !weekly.draft }
53
53
  ].filter((row) => row !== undefined)
54
54
  );
55
55
  const frameSpec = $derived(
@@ -8,7 +8,7 @@ export interface WeeklyEditMap {
8
8
  title?: EditDescriptor;
9
9
  excerpt?: EditDescriptor;
10
10
  image?: PropertyDescriptor;
11
- /** Editorial state row; value derives from `draft` ('false' when draft). */
11
+ /** Editorial-state `flag` row; on while the weekly is not a draft. */
12
12
  status?: PropertyDescriptor;
13
13
  /** Accessible name for the frame, e.g. "Weekly #12". */
14
14
  label?: string;
@@ -4,4 +4,4 @@ export type { EditMessages } from './edit-messages.js';
4
4
  export { defaultMessages } from './messages.js';
5
5
  export { default as UiProvider } from './UiProvider.svelte';
6
6
  export { BASE_LOCALE, LOCALES } from './types.js';
7
- export type { Locale, SiteLink, UiConfig, UiConfigInput, UiMessages } from './types.js';
7
+ export type { Locale, NotParameterized, ParameterizedKey, ParameterlessKey, SiteLink, UiConfig, UiConfigInput, UiMessages } from './types.js';
@@ -104,5 +104,9 @@ export const defaultMessages = {
104
104
  category_lab: () => 'Laboratori',
105
105
  category_education: () => 'Educació',
106
106
  category_collaboration: () => 'Col·laboració',
107
- category_press: () => 'Premsa'
107
+ category_press: () => 'Premsa',
108
+ status_published: () => 'Publicat',
109
+ status_draft: () => 'Esborrany',
110
+ status_open: () => 'Oberta',
111
+ status_closed: () => 'Tancada'
108
112
  };
@@ -142,7 +142,39 @@ export interface UiMessages {
142
142
  category_education(): string;
143
143
  category_collaboration(): string;
144
144
  category_press(): string;
145
+ /**
146
+ * Editorial state, as the panel's `flag` rows word it: a card's
147
+ * published/draft pair, an opening's open/closed pair. Catalog keys rather
148
+ * than editor strings because the CMS edits them per locale like any other
149
+ * wording — and required, like every key but the *Href pair, because
150
+ * `messages` is all-or-nothing (see ./messages.js).
151
+ */
152
+ status_published(): string;
153
+ status_draft(): string;
154
+ status_open(): string;
155
+ status_closed(): string;
145
156
  }
157
+ /**
158
+ * The catalog keys whose message takes NO parameters — the only ones edit
159
+ * mode may offer as text, inline or in a panel: editing the RENDERED text of
160
+ * a parameterized message (`pagination_status`, `weeklie_number`) would
161
+ * overwrite its template with one interpolation. Derived from the message
162
+ * signatures, so the rule is checked where a key is named; the optional
163
+ * *Href pair qualifies like any other zero-argument message.
164
+ */
165
+ export type ParameterlessKey = {
166
+ [K in keyof UiMessages]-?: NonNullable<UiMessages[K]> extends () => string ? K : never;
167
+ }[keyof UiMessages];
168
+ /** The complement: keys whose message interpolates something. */
169
+ export type ParameterizedKey = Exclude<keyof UiMessages, ParameterlessKey>;
170
+ /**
171
+ * A key a chrome PROPERTY may name: any string but one of the package's own
172
+ * parameterized keys. Wider than `ParameterlessKey` on purpose — the site's
173
+ * catalog has wording the components never render themselves (a search
174
+ * placeholder is the host's), and that key the host vouches for; the
175
+ * package refuses only what it can check.
176
+ */
177
+ export type NotParameterized<K extends string> = K extends ParameterizedKey ? never : K;
146
178
  /**
147
179
  * Everything the components read from their host app, resolved. Apps provide
148
180
  * a partial (`UiConfigInput`) through UiProvider; components read this
@@ -179,10 +211,10 @@ export interface UiConfig {
179
211
  * Edit descriptor for one of THIS config's message strings, by catalog
180
212
  * key — the interface-wording half of edit mode. Optional and undefined by
181
213
  * default, so a read-only app (and every story/test) renders the messages
182
- * as plain text. A CMS-shaped host supplies it alongside an EditAdapter;
183
- * components offer only their parameterless, plain-text message sites.
214
+ * as plain text. A CMS-shaped host supplies it alongside an EditAdapter.
215
+ * The key type is the rule: only a `ParameterlessKey` may edit as text.
184
216
  */
185
- messageEdit?: (key: string) => EditDescriptor | undefined;
217
+ messageEdit?: (key: ParameterlessKey) => EditDescriptor | undefined;
186
218
  }
187
219
  /** What an app hands UiProvider: any subset; the rest keeps package defaults. */
188
220
  export interface UiConfigInput {
@@ -17,6 +17,10 @@ export type { MilestoneEditMap } from './components/timeline/TimelineMilestone.s
17
17
  export { default as WeeklieCard } from './components/weeklies/WeeklieCard.svelte';
18
18
  export type { WeeklyEditMap } from './components/weeklies/WeeklieCard.svelte';
19
19
  export * from './content/index.js';
20
- export { MILESTONE_CATEGORY_COLOR, milestoneCategoryLabel } from './utils/milestones.js';
20
+ export { MILESTONE_CATEGORY_COLOR, matchesMilestoneFilter, milestoneCategoryLabel } from './utils/milestones.js';
21
+ export { createUrlFilters } from './utils/url-filters.svelte.js';
22
+ export type { UrlFilters, UrlFiltersConfig } from './utils/url-filters.svelte.js';
23
+ export { createWeeklyList, WEEKLY_LIST_DEFAULTS } from './utils/weekly-list.svelte.js';
24
+ export type { WeeklyList, WeeklyListConfig, WeeklyListFilters, WeeklyListPage, WeeklyListServerData } from './utils/weekly-list.svelte.js';
21
25
  export { contactCategoryLabel } from './utils/contact.js';
22
26
  export { formatDate, yearOf } from './utils/dates.js';
@@ -12,6 +12,9 @@ export { default as Timeline } from './components/timeline/Timeline.svelte';
12
12
  export { default as TimelineMilestone } from './components/timeline/TimelineMilestone.svelte';
13
13
  export { default as WeeklieCard } from './components/weeklies/WeeklieCard.svelte';
14
14
  export * from './content/index.js';
15
- export { MILESTONE_CATEGORY_COLOR, milestoneCategoryLabel } from './utils/milestones.js';
15
+ export { MILESTONE_CATEGORY_COLOR, matchesMilestoneFilter, milestoneCategoryLabel } from './utils/milestones.js';
16
+ // The two list rules: URL-mirrored filters, and the weeklies index over them.
17
+ export { createUrlFilters } from './utils/url-filters.svelte.js';
18
+ export { createWeeklyList, WEEKLY_LIST_DEFAULTS } from './utils/weekly-list.svelte.js';
16
19
  export { contactCategoryLabel } from './utils/contact.js';
17
20
  export { formatDate, yearOf } from './utils/dates.js';
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { PropertyDescriptor } from '../types.js';
2
+ import type { PropertyDescriptor, PropertyValue } from '../types.js';
3
3
  import PropertyRow from './PropertyRow.svelte';
4
4
 
5
5
  /**
@@ -8,7 +8,7 @@
8
8
  * knows nothing about the entity — the descriptors say everything.
9
9
  */
10
10
  interface Props {
11
- rows: { descriptor: PropertyDescriptor; value: string | null }[];
11
+ rows: { descriptor: PropertyDescriptor; value: PropertyValue }[];
12
12
  }
13
13
 
14
14
  let { rows }: Props = $props();
@@ -1,4 +1,4 @@
1
- import type { PropertyDescriptor } from '../types.js';
1
+ import type { PropertyDescriptor, PropertyValue } from '../types.js';
2
2
  /**
3
3
  * The property form an EditFrame's popover holds: one PropertyRow per
4
4
  * entry, each with its own independent commit lifecycle. The component
@@ -7,7 +7,7 @@ import type { PropertyDescriptor } from '../types.js';
7
7
  interface Props {
8
8
  rows: {
9
9
  descriptor: PropertyDescriptor;
10
- value: string | null;
10
+ value: PropertyValue;
11
11
  }[];
12
12
  }
13
13
  declare const EditPanel: import("svelte").Component<Props, {}, "">;