@vit-foundation/ui 0.24.2 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +11 -11
  2. package/dist/community.d.ts +12 -3
  3. package/dist/community.js +7 -3
  4. package/dist/components/pages/GetInvolvedPage.svelte +139 -0
  5. package/dist/components/pages/GetInvolvedPage.svelte.d.ts +36 -0
  6. package/dist/components/pages/HomePage.svelte +256 -0
  7. package/dist/components/pages/HomePage.svelte.d.ts +36 -0
  8. package/dist/components/pages/LegalPage.svelte +39 -0
  9. package/dist/components/pages/LegalPage.svelte.d.ts +14 -0
  10. package/dist/components/pages/ProjectPage.svelte +122 -0
  11. package/dist/components/pages/ProjectPage.svelte.d.ts +15 -0
  12. package/dist/components/pages/TransparencyPage.svelte +164 -0
  13. package/dist/components/pages/TransparencyPage.svelte.d.ts +40 -0
  14. package/dist/components/pages/WeekliesPage.svelte +184 -0
  15. package/dist/components/pages/WeekliesPage.svelte.d.ts +46 -0
  16. package/dist/components/pages/WeeklyPage.svelte +198 -0
  17. package/dist/components/pages/WeeklyPage.svelte.d.ts +33 -0
  18. package/dist/components/pages/WhatWeDoPage.svelte +107 -0
  19. package/dist/components/pages/WhatWeDoPage.svelte.d.ts +27 -0
  20. package/dist/components/pages/WhoWeArePage.svelte +108 -0
  21. package/dist/components/pages/WhoWeArePage.svelte.d.ts +32 -0
  22. package/dist/components/ui/Modal.svelte +32 -11
  23. package/dist/components/ui/Modal.svelte.d.ts +17 -1
  24. package/dist/config/context.d.ts +0 -6
  25. package/dist/config/context.js +1 -1
  26. package/dist/config/index.d.ts +2 -2
  27. package/dist/config/index.js +1 -1
  28. package/dist/config/messages.js +22 -0
  29. package/dist/config/types.d.ts +36 -0
  30. package/dist/content/pages.d.ts +64 -0
  31. package/dist/content/pages.js +71 -0
  32. package/dist/content/types.d.ts +45 -0
  33. package/dist/content/types.js +0 -5
  34. package/dist/content-components.d.ts +22 -1
  35. package/dist/content-components.js +17 -1
  36. package/dist/contract.d.ts +11 -4
  37. package/dist/contract.js +11 -3
  38. package/dist/edit/Editable.svelte +17 -161
  39. package/dist/edit/Editable.svelte.d.ts +2 -50
  40. package/dist/edit/chrome/AddSlot.svelte +11 -78
  41. package/dist/edit/chrome/AddSlot.svelte.d.ts +2 -15
  42. package/dist/edit/chrome/EditFrame.svelte +15 -158
  43. package/dist/edit/chrome/EditFrame.svelte.d.ts +2 -31
  44. package/dist/edit/chrome/EditPanel.svelte +14 -22
  45. package/dist/edit/chrome/EditPanel.svelte.d.ts +2 -13
  46. package/dist/edit/chrome/LinkEdit.svelte +13 -277
  47. package/dist/edit/chrome/LinkEdit.svelte.d.ts +2 -48
  48. package/dist/edit/chrome-props.d.ts +96 -0
  49. package/dist/edit/chrome-props.js +1 -0
  50. package/dist/edit/context.d.ts +38 -5
  51. package/dist/edit/context.js +18 -7
  52. package/dist/edit/index.d.ts +6 -4
  53. package/dist/edit/index.js +4 -3
  54. package/dist/edit/live/AddSlot.svelte +83 -0
  55. package/dist/edit/live/AddSlot.svelte.d.ts +4 -0
  56. package/dist/edit/live/EditFrame.svelte +153 -0
  57. package/dist/edit/live/EditFrame.svelte.d.ts +4 -0
  58. package/dist/edit/live/EditPanel.svelte +25 -0
  59. package/dist/edit/live/EditPanel.svelte.d.ts +4 -0
  60. package/dist/edit/live/Editable.svelte +162 -0
  61. package/dist/edit/live/Editable.svelte.d.ts +4 -0
  62. package/dist/edit/live/LinkEdit.svelte +276 -0
  63. package/dist/edit/live/LinkEdit.svelte.d.ts +4 -0
  64. package/dist/edit/{chrome → live}/PropertyRow.svelte +1 -1
  65. package/dist/edit/{commit.svelte.d.ts → live/commit.svelte.d.ts} +2 -2
  66. package/dist/edit/live/index.d.ts +10 -0
  67. package/dist/edit/live/index.js +14 -0
  68. package/package.json +1 -1
  69. package/dist/components/account/index.d.ts +0 -3
  70. package/dist/components/account/index.js +0 -2
  71. package/dist/components/auth/index.d.ts +0 -7
  72. package/dist/components/auth/index.js +0 -4
  73. package/dist/components/contact/index.d.ts +0 -2
  74. package/dist/components/contact/index.js +0 -1
  75. package/dist/content/index.d.ts +0 -4
  76. package/dist/content/index.js +0 -2
  77. /package/dist/edit/{chrome → live}/ConfirmDialog.svelte +0 -0
  78. /package/dist/edit/{chrome → live}/ConfirmDialog.svelte.d.ts +0 -0
  79. /package/dist/edit/{chrome → live}/EditPopover.svelte +0 -0
  80. /package/dist/edit/{chrome → live}/EditPopover.svelte.d.ts +0 -0
  81. /package/dist/edit/{chrome → live}/PropertyRow.svelte.d.ts +0 -0
  82. /package/dist/edit/{commit.svelte.js → live/commit.svelte.js} +0 -0
@@ -149,6 +149,42 @@ export interface UiMessages {
149
149
  category_education(): string;
150
150
  category_collaboration(): string;
151
151
  category_press(): string;
152
+ /**
153
+ * The wording the PAGE MODULES read (`components/pages/*`), REQUIRED like
154
+ * every key but the *Href pair above.
155
+ *
156
+ * ⚠ Adding them was a compile-break for any host whose `messages` object
157
+ * lacked one — `messages` is all-or-nothing (see ./messages.js), so a
158
+ * catalog that compiled against 0.24 stops compiling here rather than
159
+ * rendering a hole. Both hosts already carried every key (the website's
160
+ * Paraglide catalog, vit-brain's `ui_messages` table) because the nine
161
+ * route files read them by hand before the pages moved in; the only thing
162
+ * that changed is that the package now names what it reads. The four
163
+ * `*Href` keys are destinations edited as wording (fndvit-website#11) —
164
+ * a link's text and address are one editorial gesture, so both are keys.
165
+ */
166
+ nav_whoWeAre(): string;
167
+ nav_whatWeDo(): string;
168
+ nav_getInvolved(): string;
169
+ nav_weeklies(): string;
170
+ common_seeAll(): string;
171
+ common_seeAllHref(): string;
172
+ cta_meetTeam(): string;
173
+ cta_meetTeamHref(): string;
174
+ cta_contactUs(): string;
175
+ cta_contactUsHref(): string;
176
+ weeklies_searchPlaceholder(): string;
177
+ weeklies_exploreOne(): string;
178
+ weeklies_filterLabel(): string;
179
+ weeklies_searchResultsLabel(): string;
180
+ weeklies_loadError(): string;
181
+ weeklies_empty(): string;
182
+ timeline_searchPlaceholder(): string;
183
+ timeline_filterLabel(): string;
184
+ timeline_empty(): string;
185
+ back_label(): string;
186
+ weeklie_sources(): string;
187
+ weeklie_keepExploring(): string;
152
188
  /**
153
189
  * Editorial state, as the panel's `flag` rows word it: a card's
154
190
  * published/draft pair, an opening's open/closed pair. Catalog keys rather
@@ -0,0 +1,64 @@
1
+ import type { EditDescriptor } from '../edit/types.js';
2
+ /**
3
+ * Page copy: the editorial blocks of a static page, stored per page and
4
+ * section key in the website's `page_content` table. This module is the
5
+ * only declaration of which keys each page has — the website's `seed.sql`
6
+ * supplies their text and its repository integration test asserts the two
7
+ * agree, so a renamed or dropped row fails that suite instead of silently
8
+ * rendering nothing. vit-brain's `page-copy-keys.test.ts` pins the same set
9
+ * against the live table from the other side.
10
+ *
11
+ * It lived in fndvit-website's `schemas/page-copy.ts` and was restated in
12
+ * vit-brain's `mirror.ts` (with the FORMAT each block edits in — that half
13
+ * stays in brain, ADR-0019). Now that the page modules render from this
14
+ * package, the keys a page reads are the package's fact: a module indexes
15
+ * `copy.hero_title`, so the tuple that makes that compile has to live beside
16
+ * it. Component-free on purpose — `./contract` re-exports it for a host's
17
+ * `+page.server.ts` and a CMS's producer alike.
18
+ *
19
+ * UI chrome (labels, buttons, error messages) is not page copy: it lives in
20
+ * `UiMessages` and ships with the build.
21
+ */
22
+ /**
23
+ * The "why get in touch" blocks, rendered as one list on /get-involved.
24
+ *
25
+ * Named because a run of keys being a *group* is a fact about the copy, and it
26
+ * needs the same single owner the keys themselves have: the page used to
27
+ * restate these five, so adding a sixth reason here and to the seed left the
28
+ * page silently ignoring it — no type error, no failing test.
29
+ */
30
+ export declare const GET_INVOLVED_REASON_KEYS: readonly ["reason_collaborate", "reason_event", "reason_press", "reason_brand", "reason_other"];
31
+ export declare const PAGE_COPY_KEYS: {
32
+ readonly home: readonly ["hero_title", "hero_subtitle", "milestones_heading", "weeklies_heading", "weeklies_intro", "know_more_heading"];
33
+ readonly 'what-we-do': readonly ["latest_heading", "collaborations_heading", "collaborations_intro", "passion_heading", "passion_intro"];
34
+ readonly 'who-we-are': readonly ["team_heading", "team_intro", "board_heading", "board_intro", "collaborators_heading"];
35
+ readonly weeklies: readonly ["intro"];
36
+ readonly 'get-involved': readonly ["contact_heading", "contact_intro", "reason_collaborate", "reason_event", "reason_press", "reason_brand", "reason_other", "funding_heading", "funding_body", "careers_heading", "careers_intro"];
37
+ readonly transparency: readonly ["heading", "intro"];
38
+ readonly legal: readonly ["heading", "body"];
39
+ };
40
+ /** A page that has editorial copy. Matches the `page_content.page` column. */
41
+ export type PageId = keyof typeof PAGE_COPY_KEYS;
42
+ /** The section keys declared for one page. */
43
+ export type CopyKey<P extends PageId> = (typeof PAGE_COPY_KEYS)[P][number];
44
+ /**
45
+ * One page's copy, one locale already applied. Every declared key is present:
46
+ * a row missing from the database resolves to an empty string rather than
47
+ * `undefined`.
48
+ *
49
+ * The page modules do NOT guard for that empty string. It is not a state the
50
+ * site is meant to render around — the website's repository integration test
51
+ * asserts that every declared key resolves to non-empty copy, so a dropped or
52
+ * renamed row fails the suite. Guarding at the render site instead puts the
53
+ * decision back at every call site, which is what the assertion exists to
54
+ * avoid (the same answer `CopyIntro` gives).
55
+ */
56
+ export type PageCopy<P extends PageId> = Record<CopyKey<P>, string>;
57
+ /**
58
+ * How a page module asks for one copy block's edit descriptor: by the key it
59
+ * renders, so a module cannot name a block its page does not declare. A
60
+ * read-only host passes nothing; a CMS answers with a `page-copy` ref for the
61
+ * locale being edited (vit-brain's `pageCopyEditor`), or `undefined` to leave
62
+ * one block alone.
63
+ */
64
+ export type CopyEditFor<P extends PageId> = (key: CopyKey<P>) => EditDescriptor | undefined;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Page copy: the editorial blocks of a static page, stored per page and
3
+ * section key in the website's `page_content` table. This module is the
4
+ * only declaration of which keys each page has — the website's `seed.sql`
5
+ * supplies their text and its repository integration test asserts the two
6
+ * agree, so a renamed or dropped row fails that suite instead of silently
7
+ * rendering nothing. vit-brain's `page-copy-keys.test.ts` pins the same set
8
+ * against the live table from the other side.
9
+ *
10
+ * It lived in fndvit-website's `schemas/page-copy.ts` and was restated in
11
+ * vit-brain's `mirror.ts` (with the FORMAT each block edits in — that half
12
+ * stays in brain, ADR-0019). Now that the page modules render from this
13
+ * package, the keys a page reads are the package's fact: a module indexes
14
+ * `copy.hero_title`, so the tuple that makes that compile has to live beside
15
+ * it. Component-free on purpose — `./contract` re-exports it for a host's
16
+ * `+page.server.ts` and a CMS's producer alike.
17
+ *
18
+ * UI chrome (labels, buttons, error messages) is not page copy: it lives in
19
+ * `UiMessages` and ships with the build.
20
+ */
21
+ /**
22
+ * The "why get in touch" blocks, rendered as one list on /get-involved.
23
+ *
24
+ * Named because a run of keys being a *group* is a fact about the copy, and it
25
+ * needs the same single owner the keys themselves have: the page used to
26
+ * restate these five, so adding a sixth reason here and to the seed left the
27
+ * page silently ignoring it — no type error, no failing test.
28
+ */
29
+ export const GET_INVOLVED_REASON_KEYS = [
30
+ 'reason_collaborate',
31
+ 'reason_event',
32
+ 'reason_press',
33
+ 'reason_brand',
34
+ 'reason_other'
35
+ ];
36
+ export const PAGE_COPY_KEYS = {
37
+ home: [
38
+ 'hero_title',
39
+ 'hero_subtitle',
40
+ 'milestones_heading',
41
+ 'weeklies_heading',
42
+ 'weeklies_intro',
43
+ 'know_more_heading'
44
+ ],
45
+ 'what-we-do': [
46
+ 'latest_heading',
47
+ 'collaborations_heading',
48
+ 'collaborations_intro',
49
+ 'passion_heading',
50
+ 'passion_intro'
51
+ ],
52
+ 'who-we-are': [
53
+ 'team_heading',
54
+ 'team_intro',
55
+ 'board_heading',
56
+ 'board_intro',
57
+ 'collaborators_heading'
58
+ ],
59
+ weeklies: ['intro'],
60
+ 'get-involved': [
61
+ 'contact_heading',
62
+ 'contact_intro',
63
+ ...GET_INVOLVED_REASON_KEYS,
64
+ 'funding_heading',
65
+ 'funding_body',
66
+ 'careers_heading',
67
+ 'careers_intro'
68
+ ],
69
+ transparency: ['heading', 'intro'],
70
+ legal: ['heading', 'body']
71
+ };
@@ -3,6 +3,7 @@
3
3
  * applied. The website's zod-inferred types satisfy these structurally; the
4
4
  * package states them itself so consumers owe it no schema library.
5
5
  */
6
+ import type { EditDescriptor } from '../edit/types.js';
6
7
  /** The bounds of one form field; Field turns them into length attributes. */
7
8
  export interface FieldConstraint {
8
9
  min?: number;
@@ -28,6 +29,26 @@ export interface WeeklyCardData {
28
29
  excerpt: string;
29
30
  imageUrl: string;
30
31
  }
32
+ /** A theme a weekly is filed under, as the /weeklies filter chips render it. */
33
+ export interface ThemeData {
34
+ slug: string;
35
+ name: string;
36
+ }
37
+ /** One cited source of a weekly. */
38
+ export interface WeeklySourceData {
39
+ label: string;
40
+ url: string;
41
+ }
42
+ /**
43
+ * A weekly's OWN page: the card, plus what only the detail renders. The
44
+ * website's `weeklyDetailSchema` carries `related` too; the page module
45
+ * takes that list as its own prop, because vit-brain loads it separately.
46
+ */
47
+ export interface WeeklyArticleData extends WeeklyCardData {
48
+ body: string | null;
49
+ instagramUrl: string | null;
50
+ sources: WeeklySourceData[];
51
+ }
31
52
  /** The kinds a project can have, in display order — a host's enum derives from this. */
32
53
  export declare const PROJECT_KINDS: readonly ["collaboration", "passion"];
33
54
  export type ProjectKind = (typeof PROJECT_KINDS)[number];
@@ -45,6 +66,28 @@ export interface ProjectCardData {
45
66
  externalUrl: string | null;
46
67
  hasStory: boolean;
47
68
  }
69
+ /**
70
+ * A project's OWN page: the card, plus what only the detail renders. The
71
+ * website's `projectDetailSchema` and vit-brain's `toProjectArticle` both
72
+ * answer this shape.
73
+ */
74
+ export interface ProjectArticleData extends ProjectCardData {
75
+ body: string | null;
76
+ previewImageUrl: string | null;
77
+ }
78
+ /**
79
+ * A weekly's or a project's OWN page: the same localized fields as its card,
80
+ * except that `body` renders as rich text there. Which surface a field is
81
+ * rendered on is the page's fact, and these two are the only pages with one
82
+ * — so the map is shared by `ProjectPage` and `WeeklyPage` and answered by
83
+ * one producer (vit-brain's `articleEdit`). All members optional, like every
84
+ * `*EditMap`: a host may open the body and leave the title alone.
85
+ */
86
+ export interface ArticleEdit {
87
+ title?: EditDescriptor;
88
+ excerpt?: EditDescriptor;
89
+ body?: EditDescriptor;
90
+ }
48
91
  /**
49
92
  * The timeline's categories, in DISPLAY ORDER — every select that offers them
50
93
  * and every host enum derives from this.
@@ -75,6 +118,8 @@ export interface MilestoneData {
75
118
  linkUrl: string | null;
76
119
  }
77
120
  export interface TeamMemberData {
121
+ /** Present only where the card is editable — a descriptor needs the row (precedent: CollaboratorData). */
122
+ id?: string | number;
78
123
  slug: string;
79
124
  name: string;
80
125
  role: string;
@@ -1,8 +1,3 @@
1
- /**
2
- * Structural shapes of the content the components render, one locale already
3
- * applied. The website's zod-inferred types satisfy these structurally; the
4
- * package states them itself so consumers owe it no schema library.
5
- */
6
1
  /** The kinds a project can have, in display order — a host's enum derives from this. */
7
2
  export const PROJECT_KINDS = ['collaboration', 'passion'];
8
3
  /**
@@ -17,7 +17,28 @@ export { default as TimelineMilestone } from './components/timeline/TimelineMile
17
17
  export type { MilestoneEditMap } from './components/timeline/TimelineMilestone.svelte';
18
18
  export { default as WeeklieCard } from './components/weeklies/WeeklieCard.svelte';
19
19
  export type { WeeklyEditMap } from './components/weeklies/WeeklieCard.svelte';
20
- export * from './content/index.js';
20
+ export { default as HomePage } from './components/pages/HomePage.svelte';
21
+ export type { HomePageEdit } from './components/pages/HomePage.svelte';
22
+ export { default as WhoWeArePage } from './components/pages/WhoWeArePage.svelte';
23
+ export type { WhoWeArePageEdit } from './components/pages/WhoWeArePage.svelte';
24
+ export { default as WhatWeDoPage } from './components/pages/WhatWeDoPage.svelte';
25
+ export type { WhatWeDoPageEdit } from './components/pages/WhatWeDoPage.svelte';
26
+ export { default as GetInvolvedPage } from './components/pages/GetInvolvedPage.svelte';
27
+ export type { GetInvolvedPageEdit } from './components/pages/GetInvolvedPage.svelte';
28
+ export { default as TransparencyPage } from './components/pages/TransparencyPage.svelte';
29
+ export type { TransparencyPageEdit } from './components/pages/TransparencyPage.svelte';
30
+ export { default as LegalPage } from './components/pages/LegalPage.svelte';
31
+ export type { LegalPageEdit } from './components/pages/LegalPage.svelte';
32
+ export { default as WeekliesPage } from './components/pages/WeekliesPage.svelte';
33
+ export type { WeekliesPageEdit } from './components/pages/WeekliesPage.svelte';
34
+ export { default as ProjectPage } from './components/pages/ProjectPage.svelte';
35
+ export { default as WeeklyPage } from './components/pages/WeeklyPage.svelte';
36
+ export { renderBody } from './content/richtext.js';
37
+ export type { RichTextBlock } from './content/richtext.js';
38
+ export { GET_INVOLVED_REASON_KEYS, PAGE_COPY_KEYS } from './content/pages.js';
39
+ export type { CopyEditFor, CopyKey, PageCopy, PageId } from './content/pages.js';
40
+ export { COMMENT_STATUSES, CONTACT_CATEGORIES, MILESTONE_CATEGORIES, PROJECT_KINDS, REACTIONS } from './content/types.js';
41
+ export type { ArticleEdit, CollaboratorData, CommentData, CommentThreadData, CommentStatus, ContactCategory, FieldConstraint, FormFailReason, JobOpeningData, MilestoneCategory, MilestoneData, ProjectArticleData, ProjectCardData, ProjectKind, Reaction, ReactionSummary, ReactionTarget, SortDirection, TeamMemberData, ThemeData, WeeklyArticleData, WeeklyCardData, WeeklySourceData } from './content/types.js';
21
42
  export { MILESTONE_CATEGORY_COLOR, matchesMilestoneFilter, milestoneCategoryLabel } from './utils/milestones.js';
22
43
  export { createUrlFilters } from './utils/url-filters.svelte.js';
23
44
  export type { UrlFilters, UrlFiltersConfig } from './utils/url-filters.svelte.js';
@@ -11,7 +11,23 @@ export { default as TeamMemberCard } from './components/team/TeamMemberCard.svel
11
11
  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
- export * from './content/index.js';
14
+ // The nine PAGE modules: a website route is one tag over the site's read
15
+ // projection, and a CMS opens it with the module's `*PageEdit` map. Same door
16
+ // as the cards they compose — a page is content that renders, not chrome.
17
+ export { default as HomePage } from './components/pages/HomePage.svelte';
18
+ export { default as WhoWeArePage } from './components/pages/WhoWeArePage.svelte';
19
+ export { default as WhatWeDoPage } from './components/pages/WhatWeDoPage.svelte';
20
+ export { default as GetInvolvedPage } from './components/pages/GetInvolvedPage.svelte';
21
+ export { default as TransparencyPage } from './components/pages/TransparencyPage.svelte';
22
+ export { default as LegalPage } from './components/pages/LegalPage.svelte';
23
+ export { default as WeekliesPage } from './components/pages/WeekliesPage.svelte';
24
+ export { default as ProjectPage } from './components/pages/ProjectPage.svelte';
25
+ export { default as WeeklyPage } from './components/pages/WeeklyPage.svelte';
26
+ // The content vocabularies and data shapes, beside the renderers that consume
27
+ // them. `./contract` names the component-free ones a second time, by design.
28
+ export { renderBody } from './content/richtext.js';
29
+ export { GET_INVOLVED_REASON_KEYS, PAGE_COPY_KEYS } from './content/pages.js';
30
+ export { COMMENT_STATUSES, CONTACT_CATEGORIES, MILESTONE_CATEGORIES, PROJECT_KINDS, REACTIONS } from './content/types.js';
15
31
  export { MILESTONE_CATEGORY_COLOR, matchesMilestoneFilter, milestoneCategoryLabel } from './utils/milestones.js';
16
32
  // The two list rules: URL-mirrored filters, and the weeklies index over them.
17
33
  export { createUrlFilters } from './utils/url-filters.svelte.js';
@@ -17,9 +17,13 @@
17
17
  * is what makes it a promise rather than a description: adding a `.svelte`
18
18
  * anywhere beneath it fails here rather than in a consumer's server build.
19
19
  *
20
- * It adds nothing. Every name below is already exported from a semantic
21
- * subpath and stays there — this is a second door into the same rooms, not a
22
- * new floor, and the barrel remains the surface a component host reads.
20
+ * It adds almost nothing. Every name below is already exported from a
21
+ * semantic subpath and stays there — this is a second door into the same
22
+ * rooms, not a new floor, and the barrel remains the surface a component host
23
+ * reads. `contract-names.test.ts` holds that to be true, name by name and from
24
+ * the same source module; its `CONTRACT_ONLY` list names the three exceptions
25
+ * (the two destination classifiers and the title format), each with its
26
+ * reason, so a fourth cannot arrive unexplained.
23
27
  *
24
28
  * It was cut to fit ONE consumer, and the second one did not fit through it.
25
29
  * vit-brain's four restatements were the motive, so the first version carried
@@ -38,7 +42,7 @@
38
42
  * the guard walks them like the rest.
39
43
  */
40
44
  export { LOCALES, BASE_LOCALE } from './config/types.js';
41
- export type { Locale, NotParameterized, ParameterizedKey, ParameterlessKey, SiteLink, UiMessages } from './config/types.js';
45
+ export type { Locale, NotParameterized, ParameterlessKey, SiteLink, UiMessages } from './config/types.js';
42
46
  export { localize } from './edit/types.js';
43
47
  export type { CollectionRef, ContentRef, EditableEntity, EditDescriptor, EntityOp, LocalizedText, PropertyDescriptor, PropertyOption, PropertyType, PropertyValue } from './edit/types.js';
44
48
  export { REACTIONS } from './content/types.js';
@@ -47,6 +51,9 @@ export { MILESTONE_CATEGORIES, PROJECT_KINDS } from './content/types.js';
47
51
  export type { MilestoneCategory, ProjectKind } from './content/types.js';
48
52
  export { WEEKLY_LIST_DEFAULTS, WEEKLY_LIST_PARAMS, parseWeeklyListUrl } from './utils/weekly-list-contract.js';
49
53
  export type { WeeklyListFilters, WeeklyListPage, WeeklyListServerData } from './utils/weekly-list-contract.js';
54
+ export { GET_INVOLVED_REASON_KEYS, PAGE_COPY_KEYS } from './content/pages.js';
55
+ export type { CopyEditFor, CopyKey, PageCopy, PageId } from './content/pages.js';
56
+ export type { ArticleEdit, ProjectArticleData, ThemeData, WeeklyArticleData, WeeklySourceData } from './content/types.js';
50
57
  export { CONTACT_CATEGORIES } from './content/types.js';
51
58
  export { COMMENT_STATUSES } from './content/types.js';
52
59
  export type { CommentStatus, ContactCategory, FieldConstraint, FormFailReason } from './content/types.js';
package/dist/contract.js CHANGED
@@ -17,9 +17,13 @@
17
17
  * is what makes it a promise rather than a description: adding a `.svelte`
18
18
  * anywhere beneath it fails here rather than in a consumer's server build.
19
19
  *
20
- * It adds nothing. Every name below is already exported from a semantic
21
- * subpath and stays there — this is a second door into the same rooms, not a
22
- * new floor, and the barrel remains the surface a component host reads.
20
+ * It adds almost nothing. Every name below is already exported from a
21
+ * semantic subpath and stays there — this is a second door into the same
22
+ * rooms, not a new floor, and the barrel remains the surface a component host
23
+ * reads. `contract-names.test.ts` holds that to be true, name by name and from
24
+ * the same source module; its `CONTRACT_ONLY` list names the three exceptions
25
+ * (the two destination classifiers and the title format), each with its
26
+ * reason, so a fourth cannot arrive unexplained.
23
27
  *
24
28
  * It was cut to fit ONE consumer, and the second one did not fit through it.
25
29
  * vit-brain's four restatements were the motive, so the first version carried
@@ -49,6 +53,10 @@ export { MILESTONE_CATEGORIES, PROJECT_KINDS } from './content/types.js';
49
53
  // half had three spellings across two repositories and this package's own test,
50
54
  // and the round-trip case guarding them crossed against the test's copy.
51
55
  export { WEEKLY_LIST_DEFAULTS, WEEKLY_LIST_PARAMS, parseWeeklyListUrl } from './utils/weekly-list-contract.js';
56
+ // The page-copy vocabulary: which blocks each static page reads. A host's
57
+ // `+page.server.ts` builds `PageCopy<P>` from it and a CMS's producer binds
58
+ // its own per-page format table to it, both on the server.
59
+ export { GET_INVOLVED_REASON_KEYS, PAGE_COPY_KEYS } from './content/pages.js';
52
60
  export { CONTACT_CATEGORIES } from './content/types.js';
53
61
  export { COMMENT_STATUSES } from './content/types.js';
54
62
  export { COMMENT_BODY, CONTACT_MESSAGE, CONTACT_NAME, DISPLAY_NAME, EMAIL, LOGIN_PASSWORD, PASSWORD } from './forms/constraints.js';
@@ -1,14 +1,11 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
- import { getUiConfig } from '../config/context.js';
4
- import { getEditAdapter } from './context.js';
5
- import { commitState } from './commit.svelte.js';
6
- import type { EditDescriptor } from './types.js';
2
+ import type { EditableProps } from './chrome-props.js';
3
+ import { getEditAdapter, getEditChrome } from './context.js';
7
4
 
8
5
  /**
9
- * The inline-edit primitive. It renders no element of its own: the child
10
- * snippet receives the text and a bag of attributes, and spreads both onto
11
- * whatever semantic element the call site owns —
6
+ * The inline-edit primitive's GATE. It renders no element of its own: the
7
+ * child snippet receives the text and a bag of attributes, and spreads
8
+ * both onto whatever semantic element the call site owns —
12
9
  *
13
10
  * <Editable edit={descriptor} value={title}>
14
11
  * {#snippet children(text, attrs)}<h3 {...attrs}>{text}</h3>{/snippet}
@@ -20,164 +17,23 @@
20
17
  *
21
18
  * Inactive — no `edit` descriptor, no adapter in context (a read-only
22
19
  * app), or the adapter's `isEditing` off — it renders the child with an
23
- * empty attribute bag: no listeners, no wrapper, no cost.
24
- *
25
- * Active, it makes the element a plain-text `contenteditable` textbox.
26
- * The DOM owns the draft while the reader types; this component reads it
27
- * back on commit. Enter commits single-line fields (Cmd/Ctrl+Enter for
28
- * 'multiline'), Escape reverts to the last saved value, blur commits a
29
- * dirty draft. A failed save keeps the draft on screen in the error state
30
- * so nothing typed is lost. State is announced through a visually hidden
31
- * `role="status"` region, the pattern ShareRow set for its copy action.
32
- *
33
- * Styling for the affordance lives in base.css (`[data-vit-editing]`):
34
- * the element is rendered by the *caller's* markup, which scoped styles
35
- * here could never reach.
20
+ * empty attribute bag: no listeners, no wrapper, no cost. Active, it
21
+ * mounts the LIVE half the host installed (`edit/live/Editable.svelte`,
22
+ * through `setEditAdapter`'s chrome), which owns the contenteditable, the
23
+ * commit lifecycle and the announcements. This file imports none of that,
24
+ * so a renderer that imports it ships none of that (`chrome-props.ts`).
36
25
  */
37
- interface EditableAttrs {
38
- contenteditable?: 'plaintext-only' | 'true';
39
- role?: 'textbox';
40
- 'aria-label'?: string;
41
- 'aria-multiline'?: 'true';
42
- 'data-vit-editing'?: 'idle' | 'dirty' | 'saving' | 'error';
43
- onbeforeinput?: (event: InputEvent) => void;
44
- oninput?: (event: Event) => void;
45
- onblur?: (event: FocusEvent) => void;
46
- onkeydown?: (event: KeyboardEvent) => void;
47
- onpaste?: (event: ClipboardEvent) => void;
48
- }
49
-
50
- interface Props {
51
- /** Identity of the string. Omit it and this is a passthrough. */
52
- edit?: EditDescriptor;
53
- value: string;
54
- children: Snippet<[string, EditableAttrs]>;
55
- }
56
-
57
- let { edit, value, children }: Props = $props();
26
+ let { edit, value, children }: EditableProps = $props();
58
27
 
59
28
  const adapter = getEditAdapter();
60
- const config = getUiConfig();
61
-
62
- /**
63
- * The commit lifecycle — the four states, the announcements, the last
64
- * persisted value and the follow-the-prop rule — is shared with
65
- * `PropertyRow` (see `./commit.svelte.js`). What stays here is where the
66
- * DRAFT lives: in the DOM, because this is a contenteditable.
67
- */
68
- // svelte-ignore state_referenced_locally
69
- const commit_ = commitState(value, config.editMessages);
70
- /** What the child snippet renders. Only rewritten while the DOM is not
71
- * being typed in — Svelte and the reader must not fight over the node. */
72
- // svelte-ignore state_referenced_locally
73
- let renderText = $state(value);
74
-
75
- $effect(() => {
76
- const adopted = commit_.follow(value);
77
- if (adopted !== null) renderText = adopted;
78
- });
29
+ const chrome = getEditChrome();
79
30
 
80
31
  const active = $derived(edit !== undefined && (adapter?.isEditing ?? false));
81
- const multiline = $derived(edit?.format === 'multiline' || edit?.format === 'richtext');
82
-
83
- /** contenteditable text, innerText keeps line breaks; NBSPs become spaces. */
84
- function textOf(element: HTMLElement): string {
85
- return (element.innerText ?? element.textContent ?? '').replace(/\u00a0/g, ' ');
86
- }
87
-
88
- function handleBeforeInput(event: InputEvent): void {
89
- // 'plaintext-only' is not universal (Firefox); strip rich insertions.
90
- if (event.inputType === 'insertFromPaste' || event.inputType === 'insertFromDrop') return;
91
- if (!multiline && event.inputType === 'insertParagraph') event.preventDefault();
92
- }
93
-
94
- function handlePaste(event: ClipboardEvent): void {
95
- // Plain text only, whatever the clipboard holds.
96
- event.preventDefault();
97
- const text = event.clipboardData?.getData('text/plain') ?? '';
98
- const selection = window.getSelection();
99
- if (!selection || selection.rangeCount === 0) return;
100
- const range = selection.getRangeAt(0);
101
- range.deleteContents();
102
- range.insertNode(document.createTextNode(multiline ? text : text.replace(/\s*\n\s*/g, ' ')));
103
- selection.collapseToEnd();
104
- commit_.markDirty();
105
- }
106
-
107
- function handleInput(): void {
108
- commit_.markDirty();
109
- }
110
-
111
- async function commit(element: HTMLElement): Promise<void> {
112
- if (!edit || !adapter) return;
113
- const draft = textOf(element).trim();
114
- if (draft === commit_.saved) {
115
- commit_.settle();
116
- return;
117
- }
118
- // A failed save leaves the draft in the DOM; the reader decides whether
119
- // to retry.
120
- await commit_.commit(draft, () => adapter.save(edit, draft));
121
- }
122
-
123
- function handleBlur(event: FocusEvent): void {
124
- const status = commit_.status;
125
- if (status === 'dirty' || status === 'error') void commit(event.currentTarget as HTMLElement);
126
- }
127
-
128
- function handleKeydown(event: KeyboardEvent): void {
129
- const element = event.currentTarget as HTMLElement;
130
- if (event.key === 'Escape') {
131
- // Svelte's cached text still equals renderText, so a state write
132
- // alone cannot repaint a node the reader has mutated: restore the
133
- // DOM directly, then settle.
134
- element.textContent = commit_.saved;
135
- commit_.revert();
136
- element.blur();
137
- return;
138
- }
139
- const commitKey = multiline
140
- ? event.key === 'Enter' && (event.metaKey || event.ctrlKey)
141
- : event.key === 'Enter';
142
- if (commitKey) {
143
- event.preventDefault();
144
- void commit(element);
145
- }
146
- }
147
-
148
- const attrs = $derived<EditableAttrs>(
149
- active
150
- ? {
151
- contenteditable: 'plaintext-only',
152
- role: 'textbox',
153
- 'aria-label': edit?.label,
154
- 'aria-multiline': multiline ? 'true' : undefined,
155
- 'data-vit-editing': commit_.status,
156
- onbeforeinput: handleBeforeInput,
157
- oninput: handleInput,
158
- onblur: handleBlur,
159
- onkeydown: handleKeydown,
160
- onpaste: handlePaste
161
- }
162
- : {}
163
- );
164
32
  </script>
165
33
 
166
- {@render children(renderText, attrs)}
167
- {#if active}
168
- <span class="status" role="status">{commit_.announcement}</span>
34
+ {#if active && chrome}
35
+ {@const Live = chrome.Editable}
36
+ <Live {edit} {value} {children} />
37
+ {:else}
38
+ {@render children(value, {})}
169
39
  {/if}
170
-
171
- <style>
172
- .status {
173
- position: absolute;
174
- width: 1px;
175
- height: 1px;
176
- padding: 0;
177
- margin: -1px;
178
- overflow: hidden;
179
- clip-path: inset(50%);
180
- white-space: nowrap;
181
- border: 0;
182
- }
183
- </style>
@@ -1,52 +1,4 @@
1
- import type { Snippet } from 'svelte';
2
- import type { EditDescriptor } from './types.js';
3
- /**
4
- * The inline-edit primitive. It renders no element of its own: the child
5
- * snippet receives the text and a bag of attributes, and spreads both onto
6
- * whatever semantic element the call site owns —
7
- *
8
- * <Editable edit={descriptor} value={title}>
9
- * {#snippet children(text, attrs)}<h3 {...attrs}>{text}</h3>{/snippet}
10
- * </Editable>
11
- *
12
- * — the same contract Field uses for its control attrs, and for the same
13
- * reason: the element (h1, p, span) is part of the document outline and
14
- * belongs to the caller, not behind a prop.
15
- *
16
- * Inactive — no `edit` descriptor, no adapter in context (a read-only
17
- * app), or the adapter's `isEditing` off — it renders the child with an
18
- * empty attribute bag: no listeners, no wrapper, no cost.
19
- *
20
- * Active, it makes the element a plain-text `contenteditable` textbox.
21
- * The DOM owns the draft while the reader types; this component reads it
22
- * back on commit. Enter commits single-line fields (Cmd/Ctrl+Enter for
23
- * 'multiline'), Escape reverts to the last saved value, blur commits a
24
- * dirty draft. A failed save keeps the draft on screen in the error state
25
- * so nothing typed is lost. State is announced through a visually hidden
26
- * `role="status"` region, the pattern ShareRow set for its copy action.
27
- *
28
- * Styling for the affordance lives in base.css (`[data-vit-editing]`):
29
- * the element is rendered by the *caller's* markup, which scoped styles
30
- * here could never reach.
31
- */
32
- interface EditableAttrs {
33
- contenteditable?: 'plaintext-only' | 'true';
34
- role?: 'textbox';
35
- 'aria-label'?: string;
36
- 'aria-multiline'?: 'true';
37
- 'data-vit-editing'?: 'idle' | 'dirty' | 'saving' | 'error';
38
- onbeforeinput?: (event: InputEvent) => void;
39
- oninput?: (event: Event) => void;
40
- onblur?: (event: FocusEvent) => void;
41
- onkeydown?: (event: KeyboardEvent) => void;
42
- onpaste?: (event: ClipboardEvent) => void;
43
- }
44
- interface Props {
45
- /** Identity of the string. Omit it and this is a passthrough. */
46
- edit?: EditDescriptor;
47
- value: string;
48
- children: Snippet<[string, EditableAttrs]>;
49
- }
50
- declare const Editable: import("svelte").Component<Props, {}, "">;
1
+ import type { EditableProps } from './chrome-props.js';
2
+ declare const Editable: import("svelte").Component<EditableProps, {}, "">;
51
3
  type Editable = ReturnType<typeof Editable>;
52
4
  export default Editable;