@vit-foundation/ui 0.25.0 → 0.27.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.
- package/README.md +2 -2
- package/dist/community.d.ts +12 -3
- package/dist/community.js +7 -3
- package/dist/components/pages/ProjectPage.svelte +3 -9
- package/dist/components/pages/ProjectPage.svelte.d.ts +3 -5
- package/dist/components/pages/WeeklyPage.svelte +7 -12
- package/dist/components/pages/WeeklyPage.svelte.d.ts +7 -9
- package/dist/components/ui/Icon.svelte +4 -0
- package/dist/components/ui/Icon.svelte.d.ts +1 -0
- package/dist/components/ui/Modal.svelte +32 -11
- package/dist/components/ui/Modal.svelte.d.ts +17 -1
- package/dist/components/ui/SelectField.svelte +207 -0
- package/dist/components/ui/SelectField.svelte.d.ts +76 -0
- package/dist/config/context.d.ts +0 -6
- package/dist/config/context.js +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -1
- package/dist/content-components.d.ts +6 -3
- package/dist/content-components.js +5 -1
- package/dist/contract.d.ts +8 -4
- package/dist/contract.js +7 -3
- package/dist/edit/Editable.svelte +17 -161
- package/dist/edit/Editable.svelte.d.ts +2 -50
- package/dist/edit/chrome/AddSlot.svelte +11 -78
- package/dist/edit/chrome/AddSlot.svelte.d.ts +2 -15
- package/dist/edit/chrome/EditFrame.svelte +15 -158
- package/dist/edit/chrome/EditFrame.svelte.d.ts +2 -31
- package/dist/edit/chrome/EditPanel.svelte +14 -22
- package/dist/edit/chrome/EditPanel.svelte.d.ts +2 -13
- package/dist/edit/chrome/LinkEdit.svelte +13 -277
- package/dist/edit/chrome/LinkEdit.svelte.d.ts +2 -48
- package/dist/edit/chrome-props.d.ts +96 -0
- package/dist/edit/chrome-props.js +1 -0
- package/dist/edit/context.d.ts +38 -5
- package/dist/edit/context.js +18 -7
- package/dist/edit/index.d.ts +6 -4
- package/dist/edit/index.js +4 -3
- package/dist/edit/live/AddSlot.svelte +83 -0
- package/dist/edit/live/AddSlot.svelte.d.ts +4 -0
- package/dist/edit/live/EditFrame.svelte +153 -0
- package/dist/edit/live/EditFrame.svelte.d.ts +4 -0
- package/dist/edit/live/EditPanel.svelte +25 -0
- package/dist/edit/live/EditPanel.svelte.d.ts +4 -0
- package/dist/edit/live/Editable.svelte +162 -0
- package/dist/edit/live/Editable.svelte.d.ts +4 -0
- package/dist/edit/live/LinkEdit.svelte +276 -0
- package/dist/edit/live/LinkEdit.svelte.d.ts +4 -0
- package/dist/edit/{chrome → live}/PropertyRow.svelte +1 -1
- package/dist/edit/{commit.svelte.d.ts → live/commit.svelte.d.ts} +2 -2
- package/dist/edit/live/index.d.ts +10 -0
- package/dist/edit/live/index.js +14 -0
- package/dist/forms/index.d.ts +1 -0
- package/dist/forms/index.js +1 -0
- package/dist/styles/base.css +1 -1
- package/dist/styles/tokens.css +4 -0
- package/package.json +1 -1
- package/dist/components/account/index.d.ts +0 -3
- package/dist/components/account/index.js +0 -2
- package/dist/components/auth/index.d.ts +0 -7
- package/dist/components/auth/index.js +0 -4
- package/dist/components/contact/index.d.ts +0 -2
- package/dist/components/contact/index.js +0 -1
- package/dist/content/index.d.ts +0 -6
- package/dist/content/index.js +0 -3
- /package/dist/edit/{chrome → live}/ConfirmDialog.svelte +0 -0
- /package/dist/edit/{chrome → live}/ConfirmDialog.svelte.d.ts +0 -0
- /package/dist/edit/{chrome → live}/EditPopover.svelte +0 -0
- /package/dist/edit/{chrome → live}/EditPopover.svelte.d.ts +0 -0
- /package/dist/edit/{chrome → live}/PropertyRow.svelte.d.ts +0 -0
- /package/dist/edit/{commit.svelte.js → live/commit.svelte.js} +0 -0
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ Everything exports flat from the root, and again grouped by role:
|
|
|
25
25
|
| Entry point | What lives there |
|
|
26
26
|
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
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
|
|
28
|
+
| [`/forms`](./docs/forms.md) | Field, SelectField, TextField, FormFeedback, FormErrorFeedback, FormResultSlot, Honeypot, LocaleField, NewsletterIntentField — plus the remote-form seam: structural types, transport constants, field bounds |
|
|
29
29
|
| [`/chrome`](./docs/components/chrome.md) | PageShell, Nav, Footer |
|
|
30
30
|
| [`/content`](./docs/components/content.md) | WeeklieCard, ProjectCard, Timeline, TimelineMilestone, TeamMemberCard, CollaboratorList, JobList, SortSelect, the nine page modules (HomePage … WeeklyPage) — plus the data shapes, the page-copy vocabulary, helpers and the two list rules (createWeeklyList, createUrlFilters) |
|
|
31
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
|
|
32
|
+
| [`/edit`](./docs/edit-mode.md) | Editable, setEditAdapter(adapter, EDIT_CHROME)/getEditAdapter, descriptors and helpers, collectionEditing, LocalizedText |
|
|
33
33
|
| [`/config`](./docs/getting-started.md#wiring-an-app-uiprovider) | UiProvider, UiConfig, the locale set, the default Catalan messages |
|
|
34
34
|
| `/contract` | The component-free half: LOCALES, BASE_LOCALE, localize, REACTIONS, PAGE_COPY_KEYS and the edit-descriptor types — the one subpath a host may import from SERVER code |
|
|
35
35
|
| `/testing` | createRemoteFormMock — the remote-form stand-in for stories and tests |
|
package/dist/community.d.ts
CHANGED
|
@@ -3,9 +3,18 @@
|
|
|
3
3
|
* and contact. Every one takes its remote form(s) as required props — the
|
|
4
4
|
* host app owns schemas and preflight; see ./forms for the seam's types.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
6
|
+
export { default as AccountPanel } from './components/account/AccountPanel.svelte';
|
|
7
|
+
export type { DeleteAccountFormInstance, LogoutFormInstance, NewsletterToggleFormInstance, UpdateNameFormInstance } from './components/account/AccountPanel.svelte';
|
|
8
|
+
export { default as NewsletterSignup } from './components/account/NewsletterSignup.svelte';
|
|
9
|
+
export { default as AuthPageShell } from './components/auth/AuthPageShell.svelte';
|
|
10
|
+
export { default as GoogleAuthForm } from './components/auth/GoogleAuthForm.svelte';
|
|
11
|
+
export type { GoogleLoginFormInstance } from './components/auth/GoogleAuthForm.svelte';
|
|
12
|
+
export { default as LoginForm } from './components/auth/LoginForm.svelte';
|
|
13
|
+
export type { LoginFormInstance, MagicLinkFormInstance } from './components/auth/LoginForm.svelte';
|
|
14
|
+
export { default as SignupForm } from './components/auth/SignupForm.svelte';
|
|
15
|
+
export type { SignupFormInstance } from './components/auth/SignupForm.svelte';
|
|
16
|
+
export { default as ContactForm } from './components/contact/ContactForm.svelte';
|
|
17
|
+
export type { ContactFormInstance } from './components/contact/ContactForm.svelte';
|
|
9
18
|
export { default as CommentSection } from './components/weeklies/CommentSection.svelte';
|
|
10
19
|
export type { CommentFormInstance } from './components/weeklies/CommentSection.svelte';
|
|
11
20
|
export { default as ReactionBar } from './components/weeklies/ReactionBar.svelte';
|
package/dist/community.js
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
* and contact. Every one takes its remote form(s) as required props — the
|
|
4
4
|
* host app owns schemas and preflight; see ./forms for the seam's types.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
6
|
+
export { default as AccountPanel } from './components/account/AccountPanel.svelte';
|
|
7
|
+
export { default as NewsletterSignup } from './components/account/NewsletterSignup.svelte';
|
|
8
|
+
export { default as AuthPageShell } from './components/auth/AuthPageShell.svelte';
|
|
9
|
+
export { default as GoogleAuthForm } from './components/auth/GoogleAuthForm.svelte';
|
|
10
|
+
export { default as LoginForm } from './components/auth/LoginForm.svelte';
|
|
11
|
+
export { default as SignupForm } from './components/auth/SignupForm.svelte';
|
|
12
|
+
export { default as ContactForm } from './components/contact/ContactForm.svelte';
|
|
9
13
|
export { default as CommentSection } from './components/weeklies/CommentSection.svelte';
|
|
10
14
|
export { default as ReactionBar } from './components/weeklies/ReactionBar.svelte';
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
<script module lang="ts">
|
|
2
|
-
import type { ArticleEdit } from '../../content/types.js';
|
|
3
|
-
|
|
4
|
-
/** What a CMS may open on a project's own page: title, excerpt, and the body as rich text. */
|
|
5
|
-
export type ProjectPageEdit = ArticleEdit;
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
1
|
<script lang="ts">
|
|
9
2
|
import { getUiConfig } from '../../config/context.js';
|
|
10
|
-
import type { ProjectArticleData } from '../../content/types.js';
|
|
3
|
+
import type { ArticleEdit, ProjectArticleData } from '../../content/types.js';
|
|
11
4
|
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
12
5
|
import Editable from '../../edit/Editable.svelte';
|
|
13
6
|
import PageShell from '../layout/PageShell.svelte';
|
|
@@ -24,7 +17,8 @@
|
|
|
24
17
|
*/
|
|
25
18
|
interface Props {
|
|
26
19
|
project: ProjectArticleData;
|
|
27
|
-
|
|
20
|
+
/** What a CMS may open on a project's own page: title, excerpt, and the body as rich text. */
|
|
21
|
+
edit?: ArticleEdit;
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
let { project, edit }: Props = $props();
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type { ArticleEdit } from '../../content/types.js';
|
|
2
|
-
/** What a CMS may open on a project's own page: title, excerpt, and the body as rich text. */
|
|
3
|
-
export type ProjectPageEdit = ArticleEdit;
|
|
4
|
-
import type { ProjectArticleData } from '../../content/types.js';
|
|
1
|
+
import type { ArticleEdit, ProjectArticleData } from '../../content/types.js';
|
|
5
2
|
/**
|
|
6
3
|
* A project's story: date, title, excerpt, hero image, the rich-text body
|
|
7
4
|
* with a share row, and — when the project points somewhere — a preview
|
|
@@ -10,7 +7,8 @@ import type { ProjectArticleData } from '../../content/types.js';
|
|
|
10
7
|
*/
|
|
11
8
|
interface Props {
|
|
12
9
|
project: ProjectArticleData;
|
|
13
|
-
|
|
10
|
+
/** What a CMS may open on a project's own page: title, excerpt, and the body as rich text. */
|
|
11
|
+
edit?: ArticleEdit;
|
|
14
12
|
}
|
|
15
13
|
declare const ProjectPage: import("svelte").Component<Props, {}, "">;
|
|
16
14
|
type ProjectPage = ReturnType<typeof ProjectPage>;
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
<script module lang="ts">
|
|
2
|
-
import type { ArticleEdit } from '../../content/types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* What a CMS may open on a weekly's own page: title, excerpt, and the body
|
|
6
|
-
* as rich text. The «Dades i fonts» and «Segueix explorant» headings and
|
|
7
|
-
* the back link edit through `config.messageEdit` over their own keys.
|
|
8
|
-
*/
|
|
9
|
-
export type WeeklyPageEdit = ArticleEdit;
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
1
|
<script lang="ts">
|
|
13
2
|
import { getUiConfig } from '../../config/context.js';
|
|
14
3
|
import type {
|
|
4
|
+
ArticleEdit,
|
|
15
5
|
CommentThreadData,
|
|
16
6
|
ReactionSummary,
|
|
17
7
|
WeeklyArticleData,
|
|
@@ -48,7 +38,12 @@
|
|
|
48
38
|
/** One preflighted instance per reply box; see CommentSection. */
|
|
49
39
|
replyFormFor: (threadId: string) => CommentFormInstance;
|
|
50
40
|
reactionForms: ReactionBarForms;
|
|
51
|
-
|
|
41
|
+
/**
|
|
42
|
+
* What a CMS may open on a weekly's own page: title, excerpt, and the body
|
|
43
|
+
* as rich text. The «Dades i fonts» and «Segueix explorant» headings and
|
|
44
|
+
* the back link edit through `config.messageEdit` over their own keys.
|
|
45
|
+
*/
|
|
46
|
+
edit?: ArticleEdit;
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
let {
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import type { ArticleEdit } from '../../content/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* What a CMS may open on a weekly's own page: title, excerpt, and the body
|
|
4
|
-
* as rich text. The «Dades i fonts» and «Segueix explorant» headings and
|
|
5
|
-
* the back link edit through `config.messageEdit` over their own keys.
|
|
6
|
-
*/
|
|
7
|
-
export type WeeklyPageEdit = ArticleEdit;
|
|
8
|
-
import type { CommentThreadData, ReactionSummary, WeeklyArticleData, WeeklyCardData } from '../../content/types.js';
|
|
1
|
+
import type { ArticleEdit, CommentThreadData, ReactionSummary, WeeklyArticleData, WeeklyCardData } from '../../content/types.js';
|
|
9
2
|
import { type CommentFormInstance } from '../weeklies/CommentSection.svelte';
|
|
10
3
|
import { type ReactionBarForms } from '../weeklies/ReactionBar.svelte';
|
|
11
4
|
/**
|
|
@@ -28,7 +21,12 @@ interface Props {
|
|
|
28
21
|
/** One preflighted instance per reply box; see CommentSection. */
|
|
29
22
|
replyFormFor: (threadId: string) => CommentFormInstance;
|
|
30
23
|
reactionForms: ReactionBarForms;
|
|
31
|
-
|
|
24
|
+
/**
|
|
25
|
+
* What a CMS may open on a weekly's own page: title, excerpt, and the body
|
|
26
|
+
* as rich text. The «Dades i fonts» and «Segueix explorant» headings and
|
|
27
|
+
* the back link edit through `config.messageEdit` over their own keys.
|
|
28
|
+
*/
|
|
29
|
+
edit?: ArticleEdit;
|
|
32
30
|
}
|
|
33
31
|
declare const WeeklyPage: import("svelte").Component<Props, {}, "">;
|
|
34
32
|
type WeeklyPage = ReturnType<typeof WeeklyPage>;
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
'M5 7h14M10 7V5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2m3 0v12a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V7m3 4v6m4-6v6',
|
|
11
11
|
duplicate: 'M9 9h10v12H9zM5 15V3h10',
|
|
12
12
|
plus: 'M12 5v14M5 12h14',
|
|
13
|
+
/* A month sheet: body, its two hanging tabs, and the rule under the
|
|
14
|
+
weekday row. One subpath list, like `trash` and `logout`. */
|
|
15
|
+
calendar:
|
|
16
|
+
'M5 6h14a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zM4 10.5h16M8.5 3.5V6M15.5 3.5V6',
|
|
13
17
|
'chevron-right': 'M9 5l7 7-7 7',
|
|
14
18
|
'chevron-left': 'M15 5l-7 7 7 7',
|
|
15
19
|
close: 'M6 6l12 12M18 6L6 18',
|
|
@@ -8,6 +8,7 @@ declare const PATHS: {
|
|
|
8
8
|
readonly trash: "M5 7h14M10 7V5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2m3 0v12a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V7m3 4v6m4-6v6";
|
|
9
9
|
readonly duplicate: "M9 9h10v12H9zM5 15V3h10";
|
|
10
10
|
readonly plus: "M12 5v14M5 12h14";
|
|
11
|
+
readonly calendar: "M5 6h14a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1zM4 10.5h16M8.5 3.5V6M15.5 3.5V6";
|
|
11
12
|
readonly 'chevron-right': "M9 5l7 7-7 7";
|
|
12
13
|
readonly 'chevron-left': "M15 5l-7 7 7 7";
|
|
13
14
|
readonly close: "M6 6l12 12M18 6L6 18";
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { onMount, type Snippet } from 'svelte';
|
|
3
3
|
import IconButton from './IconButton.svelte';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A modal over the native <dialog>: showModal() traps focus and layers the
|
|
7
|
+
* backdrop, the cancel event covers Escape, a backdrop click closes.
|
|
8
|
+
*
|
|
9
|
+
* The element is NOT part of the server-rendered document. A <dialog> is
|
|
10
|
+
* flow content, so wherever a caller mounts one inside phrasing content —
|
|
11
|
+
* a LinkEdit inside a <p>, which NewsletterSignup does — the HTML parser
|
|
12
|
+
* closes the paragraph early, Svelte reports `node_invalid_placement_ssr`
|
|
13
|
+
* on the way out and a hydration mismatch on the way back in, on every
|
|
14
|
+
* page that renders the caller. No dialog is open at first paint (opening
|
|
15
|
+
* one takes a click), so the server has nothing to say about it; the
|
|
16
|
+
* element is created after mount, by script, which the content model does
|
|
17
|
+
* not police. Placement is this module's to own — a caller cannot know
|
|
18
|
+
* what its parent is — and owning it here fixes every caller at once,
|
|
19
|
+
* ConfirmDialog included.
|
|
20
|
+
*/
|
|
21
|
+
|
|
5
22
|
interface Props {
|
|
6
23
|
open: boolean;
|
|
7
24
|
title: string;
|
|
@@ -16,9 +33,11 @@
|
|
|
16
33
|
let { open, title, onclose, closeLabel = 'Tanca', children }: Props = $props();
|
|
17
34
|
|
|
18
35
|
let dialog: HTMLDialogElement | undefined = $state();
|
|
36
|
+
let mounted = $state(false);
|
|
37
|
+
onMount(() => {
|
|
38
|
+
mounted = true;
|
|
39
|
+
});
|
|
19
40
|
|
|
20
|
-
// Native <dialog>: showModal() traps focus and layers the backdrop; the
|
|
21
|
-
// cancel event covers Escape.
|
|
22
41
|
$effect(() => {
|
|
23
42
|
if (!dialog) return;
|
|
24
43
|
if (open && !dialog.open) dialog.showModal();
|
|
@@ -32,15 +51,17 @@
|
|
|
32
51
|
}
|
|
33
52
|
</script>
|
|
34
53
|
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
<div class="
|
|
38
|
-
<
|
|
39
|
-
|
|
54
|
+
{#if mounted}
|
|
55
|
+
<dialog bind:this={dialog} oncancel={onclose} {onclick}>
|
|
56
|
+
<div class="panel">
|
|
57
|
+
<div class="top">
|
|
58
|
+
<h2>{title}</h2>
|
|
59
|
+
<IconButton icon="close" label={closeLabel} onclick={onclose} />
|
|
60
|
+
</div>
|
|
61
|
+
{@render children()}
|
|
40
62
|
</div>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</dialog>
|
|
63
|
+
</dialog>
|
|
64
|
+
{/if}
|
|
44
65
|
|
|
45
66
|
<style>
|
|
46
67
|
dialog {
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* A modal over the native <dialog>: showModal() traps focus and layers the
|
|
4
|
+
* backdrop, the cancel event covers Escape, a backdrop click closes.
|
|
5
|
+
*
|
|
6
|
+
* The element is NOT part of the server-rendered document. A <dialog> is
|
|
7
|
+
* flow content, so wherever a caller mounts one inside phrasing content —
|
|
8
|
+
* a LinkEdit inside a <p>, which NewsletterSignup does — the HTML parser
|
|
9
|
+
* closes the paragraph early, Svelte reports `node_invalid_placement_ssr`
|
|
10
|
+
* on the way out and a hydration mismatch on the way back in, on every
|
|
11
|
+
* page that renders the caller. No dialog is open at first paint (opening
|
|
12
|
+
* one takes a click), so the server has nothing to say about it; the
|
|
13
|
+
* element is created after mount, by script, which the content model does
|
|
14
|
+
* not police. Placement is this module's to own — a caller cannot know
|
|
15
|
+
* what its parent is — and owning it here fixes every caller at once,
|
|
16
|
+
* ConfirmDialog included.
|
|
17
|
+
*/
|
|
2
18
|
interface Props {
|
|
3
19
|
open: boolean;
|
|
4
20
|
title: string;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/** One option row. `value` is what the control carries: a string, always. */
|
|
3
|
+
export interface SelectOption {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
12
|
+
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A labelled `<select>` — `TextField`'s sibling for a choice out of a list.
|
|
16
|
+
*
|
|
17
|
+
* The package had none, so eleven call sites across the two hosts each grew
|
|
18
|
+
* their own. That produced four spellings of ONE navy pill, a second box
|
|
19
|
+
* skin whose corner radius was hard-coded past `--radius` because the admin
|
|
20
|
+
* theme sets that token to `999px`, and two incompatible ways of reporting
|
|
21
|
+
* the same refusal: the user editor wires `aria-invalid` and
|
|
22
|
+
* `aria-describedby`, while the record field one module over announced its
|
|
23
|
+
* rejection with a bare `role="alert()"` and left the control unmarked. A
|
|
24
|
+
* control that cannot be rendered unwired is the point here, the way
|
|
25
|
+
* `Field`'s snippet is.
|
|
26
|
+
*
|
|
27
|
+
* The value is a STRING and stays one. Both hosts had already converged on
|
|
28
|
+
* that independently — a `<select>` binds a string, and a year, a row id or
|
|
29
|
+
* a boolean flag becomes itself again in the one adapter that reads it — so
|
|
30
|
+
* a value type here would fork the component per caller.
|
|
31
|
+
*/
|
|
32
|
+
interface Props {
|
|
33
|
+
label: string;
|
|
34
|
+
id: string;
|
|
35
|
+
/**
|
|
36
|
+
* How it presents itself. Three, which is what the call sites were:
|
|
37
|
+
*
|
|
38
|
+
* - `field` — labelled box, the default.
|
|
39
|
+
* - `pill` — filled navy, for a toolbar beside a heading.
|
|
40
|
+
* - `bare` — box, label to the screen reader only, for inline chrome
|
|
41
|
+
* that already reads as labelled by what surrounds it.
|
|
42
|
+
*/
|
|
43
|
+
variant?: 'field' | 'pill' | 'bare';
|
|
44
|
+
/** Label beside the control rather than above it — a toolbar row. */
|
|
45
|
+
inline?: boolean;
|
|
46
|
+
value?: string;
|
|
47
|
+
required?: boolean;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The leading option. `disabled` makes it a placeholder the reader must
|
|
51
|
+
* choose past; without it the empty value is selectable and MEANS
|
|
52
|
+
* something — "No manager", "every table".
|
|
53
|
+
*/
|
|
54
|
+
placeholder?: { label: string; disabled?: boolean };
|
|
55
|
+
/** Options whose labels are data. Use `children` when a label is computed. */
|
|
56
|
+
options?: readonly SelectOption[];
|
|
57
|
+
children?: Snippet;
|
|
58
|
+
error?: string | null;
|
|
59
|
+
/** Standing help. Described alongside an error rather than replaced by it. */
|
|
60
|
+
hint?: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Beside the binding, not instead of it: the callers that need one are
|
|
63
|
+
* coercing the string (a year to a number) or acting on the choice (a
|
|
64
|
+
* template applied, a draft committed).
|
|
65
|
+
*/
|
|
66
|
+
onchange?: (value: string) => void;
|
|
67
|
+
/**
|
|
68
|
+
* A remote field's `.as('select')` attachment, spread instead of bound —
|
|
69
|
+
* the same branch `TextField` takes, so a remote form does not route
|
|
70
|
+
* around this module and re-declare the skin.
|
|
71
|
+
*/
|
|
72
|
+
attributes?: HTMLSelectAttributes;
|
|
73
|
+
/** Native form submit: the browser posts under this name. */
|
|
74
|
+
name?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let {
|
|
78
|
+
label,
|
|
79
|
+
id,
|
|
80
|
+
variant = 'field',
|
|
81
|
+
inline = false,
|
|
82
|
+
value = $bindable(''),
|
|
83
|
+
required = false,
|
|
84
|
+
disabled = false,
|
|
85
|
+
placeholder,
|
|
86
|
+
options,
|
|
87
|
+
children,
|
|
88
|
+
error = null,
|
|
89
|
+
hint = null,
|
|
90
|
+
onchange,
|
|
91
|
+
attributes,
|
|
92
|
+
name
|
|
93
|
+
}: Props = $props();
|
|
94
|
+
|
|
95
|
+
/** Both ids when both are showing: an error does not hide standing help. */
|
|
96
|
+
const describedBy = $derived(
|
|
97
|
+
[error ? `${id}-error` : null, hint ? `${id}-hint` : null].filter(Boolean).join(' ') ||
|
|
98
|
+
undefined
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const shared = $derived({
|
|
102
|
+
id,
|
|
103
|
+
name,
|
|
104
|
+
required,
|
|
105
|
+
disabled,
|
|
106
|
+
'aria-invalid': error ? true : undefined,
|
|
107
|
+
'aria-describedby': describedBy
|
|
108
|
+
});
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
{#snippet body()}
|
|
112
|
+
{#if placeholder}
|
|
113
|
+
<option value="" disabled={placeholder.disabled}>{placeholder.label}</option>
|
|
114
|
+
{/if}
|
|
115
|
+
{#each options ?? [] as option (option.value)}
|
|
116
|
+
<option value={option.value} disabled={option.disabled}>{option.label}</option>
|
|
117
|
+
{/each}
|
|
118
|
+
{@render children?.()}
|
|
119
|
+
{/snippet}
|
|
120
|
+
|
|
121
|
+
<div class="select-field" class:inline class:pill={variant === 'pill'}>
|
|
122
|
+
<label for={id} class:visually-hidden={variant === 'bare'}>{label}</label>
|
|
123
|
+
{#if attributes}
|
|
124
|
+
<!-- The form owns the value and its name. -->
|
|
125
|
+
<select {...shared} {...attributes}>{@render body()}</select>
|
|
126
|
+
{:else}
|
|
127
|
+
<select {...shared} bind:value onchange={(event) => onchange?.(event.currentTarget.value)}>
|
|
128
|
+
{@render body()}
|
|
129
|
+
</select>
|
|
130
|
+
{/if}
|
|
131
|
+
{#if hint}
|
|
132
|
+
<p class="hint" id="{id}-hint">{hint}</p>
|
|
133
|
+
{/if}
|
|
134
|
+
{#if error}
|
|
135
|
+
<p class="error" id="{id}-error">{error}</p>
|
|
136
|
+
{/if}
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<style>
|
|
140
|
+
.select-field {
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
gap: var(--space-1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.select-field.inline {
|
|
147
|
+
flex-direction: row;
|
|
148
|
+
align-items: center;
|
|
149
|
+
gap: var(--space-2);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
label {
|
|
153
|
+
font-size: var(--text-sm);
|
|
154
|
+
font-weight: 500;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
select {
|
|
158
|
+
border: 1px solid var(--color-axis);
|
|
159
|
+
/* Not `--radius`: the admin theme sets that to 999px for its pill
|
|
160
|
+
buttons, which is why both of that host's boxed selects hard-coded a
|
|
161
|
+
corner past it. `--radius-control` is the input corner in both. */
|
|
162
|
+
border-radius: var(--radius-control);
|
|
163
|
+
background: var(--color-surface);
|
|
164
|
+
padding: 0.35rem 0.6rem;
|
|
165
|
+
font-family: var(--font-sans);
|
|
166
|
+
font-size: var(--text-base);
|
|
167
|
+
color: var(--color-ink);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
select:focus-visible {
|
|
171
|
+
outline: none;
|
|
172
|
+
border-color: var(--color-navy);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
select:disabled {
|
|
176
|
+
opacity: 0.5;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* The filled toolbar control: a heading's neighbour, not a form field. It
|
|
180
|
+
takes its content's width — a form field stretches, a pill does not. */
|
|
181
|
+
.pill select {
|
|
182
|
+
align-self: flex-start;
|
|
183
|
+
border-color: var(--color-navy);
|
|
184
|
+
border-radius: 999px;
|
|
185
|
+
background: var(--color-navy);
|
|
186
|
+
color: var(--color-surface);
|
|
187
|
+
padding: 0.3rem 0.8rem;
|
|
188
|
+
font-size: var(--text-sm);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.pill select:focus-visible {
|
|
192
|
+
outline: 2px solid var(--color-navy);
|
|
193
|
+
outline-offset: 2px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.hint {
|
|
197
|
+
color: var(--color-ink-muted);
|
|
198
|
+
font-size: 0.8rem;
|
|
199
|
+
margin: 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.error {
|
|
203
|
+
color: var(--series-8);
|
|
204
|
+
font-size: 0.8rem;
|
|
205
|
+
margin: 0;
|
|
206
|
+
}
|
|
207
|
+
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** One option row. `value` is what the control carries: a string, always. */
|
|
2
|
+
export interface SelectOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
import type { Snippet } from 'svelte';
|
|
8
|
+
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
9
|
+
/**
|
|
10
|
+
* A labelled `<select>` — `TextField`'s sibling for a choice out of a list.
|
|
11
|
+
*
|
|
12
|
+
* The package had none, so eleven call sites across the two hosts each grew
|
|
13
|
+
* their own. That produced four spellings of ONE navy pill, a second box
|
|
14
|
+
* skin whose corner radius was hard-coded past `--radius` because the admin
|
|
15
|
+
* theme sets that token to `999px`, and two incompatible ways of reporting
|
|
16
|
+
* the same refusal: the user editor wires `aria-invalid` and
|
|
17
|
+
* `aria-describedby`, while the record field one module over announced its
|
|
18
|
+
* rejection with a bare `role="alert()"` and left the control unmarked. A
|
|
19
|
+
* control that cannot be rendered unwired is the point here, the way
|
|
20
|
+
* `Field`'s snippet is.
|
|
21
|
+
*
|
|
22
|
+
* The value is a STRING and stays one. Both hosts had already converged on
|
|
23
|
+
* that independently — a `<select>` binds a string, and a year, a row id or
|
|
24
|
+
* a boolean flag becomes itself again in the one adapter that reads it — so
|
|
25
|
+
* a value type here would fork the component per caller.
|
|
26
|
+
*/
|
|
27
|
+
interface Props {
|
|
28
|
+
label: string;
|
|
29
|
+
id: string;
|
|
30
|
+
/**
|
|
31
|
+
* How it presents itself. Three, which is what the call sites were:
|
|
32
|
+
*
|
|
33
|
+
* - `field` — labelled box, the default.
|
|
34
|
+
* - `pill` — filled navy, for a toolbar beside a heading.
|
|
35
|
+
* - `bare` — box, label to the screen reader only, for inline chrome
|
|
36
|
+
* that already reads as labelled by what surrounds it.
|
|
37
|
+
*/
|
|
38
|
+
variant?: 'field' | 'pill' | 'bare';
|
|
39
|
+
/** Label beside the control rather than above it — a toolbar row. */
|
|
40
|
+
inline?: boolean;
|
|
41
|
+
value?: string;
|
|
42
|
+
required?: boolean;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The leading option. `disabled` makes it a placeholder the reader must
|
|
46
|
+
* choose past; without it the empty value is selectable and MEANS
|
|
47
|
+
* something — "No manager", "every table".
|
|
48
|
+
*/
|
|
49
|
+
placeholder?: {
|
|
50
|
+
label: string;
|
|
51
|
+
disabled?: boolean;
|
|
52
|
+
};
|
|
53
|
+
/** Options whose labels are data. Use `children` when a label is computed. */
|
|
54
|
+
options?: readonly SelectOption[];
|
|
55
|
+
children?: Snippet;
|
|
56
|
+
error?: string | null;
|
|
57
|
+
/** Standing help. Described alongside an error rather than replaced by it. */
|
|
58
|
+
hint?: string | null;
|
|
59
|
+
/**
|
|
60
|
+
* Beside the binding, not instead of it: the callers that need one are
|
|
61
|
+
* coercing the string (a year to a number) or acting on the choice (a
|
|
62
|
+
* template applied, a draft committed).
|
|
63
|
+
*/
|
|
64
|
+
onchange?: (value: string) => void;
|
|
65
|
+
/**
|
|
66
|
+
* A remote field's `.as('select')` attachment, spread instead of bound —
|
|
67
|
+
* the same branch `TextField` takes, so a remote form does not route
|
|
68
|
+
* around this module and re-declare the skin.
|
|
69
|
+
*/
|
|
70
|
+
attributes?: HTMLSelectAttributes;
|
|
71
|
+
/** Native form submit: the browser posts under this name. */
|
|
72
|
+
name?: string;
|
|
73
|
+
}
|
|
74
|
+
declare const SelectField: import("svelte").Component<Props, {}, "value">;
|
|
75
|
+
type SelectField = ReturnType<typeof SelectField>;
|
|
76
|
+
export default SelectField;
|
package/dist/config/context.d.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import { type UiConfig, type UiConfigInput } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* The provider-less config: identity hrefs, Catalan, no router. Complete on
|
|
4
|
-
* purpose — every component must render without a provider, which is what
|
|
5
|
-
* keeps stories and component tests free of app wiring.
|
|
6
|
-
*/
|
|
7
|
-
export declare const DEFAULT_UI_CONFIG: UiConfig;
|
|
8
2
|
/**
|
|
9
3
|
* Installs the app's config for the subtree. Takes a *thunk* so a provider
|
|
10
4
|
* whose input is a reactive prop stays live: the resolved object delegates
|
package/dist/config/context.js
CHANGED
|
@@ -8,7 +8,7 @@ const KEY = Symbol.for('@vit-foundation/ui:config');
|
|
|
8
8
|
* purpose — every component must render without a provider, which is what
|
|
9
9
|
* keeps stories and component tests free of app wiring.
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
const DEFAULT_UI_CONFIG = {
|
|
12
12
|
href: (path) => path,
|
|
13
13
|
locale: () => BASE_LOCALE,
|
|
14
14
|
locales: LOCALES,
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { getUiConfig, setUiConfig } from './context.js';
|
|
2
2
|
export { defaultEditMessages } from './edit-messages.js';
|
|
3
3
|
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, NotParameterized,
|
|
7
|
+
export type { Locale, NotParameterized, ParameterlessKey, SiteLink, UiConfig, UiConfigInput, UiMessages } from './types.js';
|
package/dist/config/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { getUiConfig, setUiConfig } from './context.js';
|
|
2
2
|
export { defaultEditMessages } from './edit-messages.js';
|
|
3
3
|
export { defaultMessages } from './messages.js';
|
|
4
4
|
export { default as UiProvider } from './UiProvider.svelte';
|
|
@@ -32,10 +32,13 @@ export type { LegalPageEdit } from './components/pages/LegalPage.svelte';
|
|
|
32
32
|
export { default as WeekliesPage } from './components/pages/WeekliesPage.svelte';
|
|
33
33
|
export type { WeekliesPageEdit } from './components/pages/WeekliesPage.svelte';
|
|
34
34
|
export { default as ProjectPage } from './components/pages/ProjectPage.svelte';
|
|
35
|
-
export type { ProjectPageEdit } from './components/pages/ProjectPage.svelte';
|
|
36
35
|
export { default as WeeklyPage } from './components/pages/WeeklyPage.svelte';
|
|
37
|
-
export
|
|
38
|
-
export
|
|
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';
|
|
39
42
|
export { MILESTONE_CATEGORY_COLOR, matchesMilestoneFilter, milestoneCategoryLabel } from './utils/milestones.js';
|
|
40
43
|
export { createUrlFilters } from './utils/url-filters.svelte.js';
|
|
41
44
|
export type { UrlFilters, UrlFiltersConfig } from './utils/url-filters.svelte.js';
|
|
@@ -23,7 +23,11 @@ export { default as LegalPage } from './components/pages/LegalPage.svelte';
|
|
|
23
23
|
export { default as WeekliesPage } from './components/pages/WeekliesPage.svelte';
|
|
24
24
|
export { default as ProjectPage } from './components/pages/ProjectPage.svelte';
|
|
25
25
|
export { default as WeeklyPage } from './components/pages/WeeklyPage.svelte';
|
|
26
|
-
|
|
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';
|
|
27
31
|
export { MILESTONE_CATEGORY_COLOR, matchesMilestoneFilter, milestoneCategoryLabel } from './utils/milestones.js';
|
|
28
32
|
// The two list rules: URL-mirrored filters, and the weeklies index over them.
|
|
29
33
|
export { createUrlFilters } from './utils/url-filters.svelte.js';
|