@vit-foundation/ui 0.3.0 → 0.7.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/dist/components/account/NewsletterSignup.svelte +30 -7
- package/dist/components/contact/ContactForm.svelte +6 -1
- package/dist/components/jobs/JobList.svelte +9 -3
- package/dist/components/layout/Footer.svelte +20 -3
- package/dist/components/layout/Footer.svelte.d.ts +3 -0
- package/dist/components/layout/Nav.svelte +17 -4
- package/dist/components/layout/Nav.svelte.d.ts +8 -0
- package/dist/components/ui/Pagination.svelte +22 -10
- package/dist/components/weeklies/CommentSection.svelte +31 -7
- package/dist/config/context.js +4 -1
- package/dist/config/types.d.ts +10 -0
- package/dist/edit/ActionLabel.svelte +45 -0
- package/dist/edit/ActionLabel.svelte.d.ts +22 -0
- package/dist/edit/helpers.d.ts +9 -0
- package/dist/edit/helpers.js +8 -0
- package/dist/edit/index.d.ts +2 -1
- package/dist/edit/index.js +2 -1
- package/dist/edit/types.d.ts +9 -2
- package/package.json +2 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getUiConfig } from '../../config/context.js';
|
|
3
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
4
|
+
import Editable from '../../edit/Editable.svelte';
|
|
3
5
|
import { withNewsletterIntent } from '../../forms/transport.js';
|
|
4
6
|
import type { NewsletterToggleFormInstance } from './AccountPanel.svelte';
|
|
5
7
|
import Button from '../ui/Button.svelte';
|
|
@@ -27,28 +29,49 @@
|
|
|
27
29
|
|
|
28
30
|
<section class="newsletter" aria-labelledby="newsletter-title">
|
|
29
31
|
<div class="inner band">
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
+
<Editable edit={config.messageEdit?.('newsletter_title')} value={msg.newsletter_title()}>
|
|
33
|
+
{#snippet children(text, attrs)}<h2 id="newsletter-title" {...attrs}>{text}</h2>{/snippet}
|
|
34
|
+
</Editable>
|
|
35
|
+
<Editable edit={config.messageEdit?.('newsletter_intro')} value={msg.newsletter_intro()}>
|
|
36
|
+
{#snippet children(text, attrs)}<p {...attrs}>{text}</p>{/snippet}
|
|
37
|
+
</Editable>
|
|
32
38
|
|
|
33
39
|
{#if account === null}
|
|
34
40
|
<p class="prompt">
|
|
35
|
-
|
|
41
|
+
<Editable
|
|
42
|
+
edit={config.messageEdit?.('newsletter_promptLoggedOut')}
|
|
43
|
+
value={msg.newsletter_promptLoggedOut()}
|
|
44
|
+
>
|
|
45
|
+
{#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
|
|
46
|
+
</Editable>
|
|
36
47
|
<Link href={withNewsletterIntent('/signup')}>{msg.comments_signupLink()}</Link>
|
|
37
48
|
·
|
|
38
49
|
<Link href={withNewsletterIntent('/login')}>{msg.comments_loginLink()}</Link>
|
|
39
50
|
</p>
|
|
40
51
|
{:else if account.newsletterSubscribed}
|
|
41
52
|
<p class="prompt">
|
|
42
|
-
|
|
53
|
+
<Editable
|
|
54
|
+
edit={config.messageEdit?.('newsletter_subscribedNote')}
|
|
55
|
+
value={msg.newsletter_subscribedNote()}
|
|
56
|
+
>
|
|
57
|
+
{#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
|
|
58
|
+
</Editable>
|
|
43
59
|
<Link href="/account">{msg.account_navLabel()}</Link>
|
|
44
60
|
</p>
|
|
45
61
|
{:else}
|
|
46
62
|
<form {...f}>
|
|
47
63
|
<input {...f.fields.action.as('hidden', 'subscribe')} />
|
|
48
64
|
<LocaleField field={f.fields.locale} />
|
|
49
|
-
<
|
|
50
|
-
{
|
|
51
|
-
|
|
65
|
+
<ActionLabel
|
|
66
|
+
edit={config.messageEdit?.('account_newsletterSubscribe')}
|
|
67
|
+
value={msg.account_newsletterSubscribe()}
|
|
68
|
+
>
|
|
69
|
+
{#snippet control()}
|
|
70
|
+
<Button type="submit" pending={f.pending}>
|
|
71
|
+
{msg.account_newsletterSubscribe()}
|
|
72
|
+
</Button>
|
|
73
|
+
{/snippet}
|
|
74
|
+
</ActionLabel>
|
|
52
75
|
</form>
|
|
53
76
|
{#if f.result}
|
|
54
77
|
<div class="feedback-slot">
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
<script lang="ts">
|
|
21
21
|
import { getUiConfig } from '../../config/context.js';
|
|
22
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
22
23
|
import { CONTACT_MESSAGE, CONTACT_NAME, EMAIL } from '../../forms/constraints.js';
|
|
23
24
|
import { CONTACT_CATEGORIES } from '../../content/types.js';
|
|
24
25
|
import { contactCategoryLabel } from '../../utils/contact.js';
|
|
@@ -120,7 +121,11 @@
|
|
|
120
121
|
<Honeypot form={f.fields} id="contact-website" />
|
|
121
122
|
|
|
122
123
|
<div class="actions">
|
|
123
|
-
<
|
|
124
|
+
<ActionLabel edit={config.messageEdit?.('contact_submit')} value={msg.contact_submit()}>
|
|
125
|
+
{#snippet control()}
|
|
126
|
+
<Button type="submit" pending={f.pending}>{msg.contact_submit()}</Button>
|
|
127
|
+
{/snippet}
|
|
128
|
+
</ActionLabel>
|
|
124
129
|
</div>
|
|
125
130
|
|
|
126
131
|
{#if f.result && !f.result.ok}
|
|
@@ -30,8 +30,12 @@
|
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
32
|
{#if jobs.length === 0}
|
|
33
|
-
<
|
|
34
|
-
|
|
33
|
+
<Editable edit={config.messageEdit?.('jobs_empty')} value={msg.jobs_empty()}>
|
|
34
|
+
{#snippet children(text, attrs)}<p class="empty" {...attrs}>{text}</p>{/snippet}
|
|
35
|
+
</Editable>
|
|
36
|
+
<Editable edit={config.messageEdit?.('jobs_newsletterNudge')} value={msg.jobs_newsletterNudge()}>
|
|
37
|
+
{#snippet children(text, attrs)}<p class="nudge" {...attrs}>{text}</p>{/snippet}
|
|
38
|
+
</Editable>
|
|
35
39
|
{:else}
|
|
36
40
|
<ul>
|
|
37
41
|
{#each jobs as job (job.slug)}
|
|
@@ -49,7 +53,9 @@
|
|
|
49
53
|
</li>
|
|
50
54
|
{/each}
|
|
51
55
|
</ul>
|
|
52
|
-
<
|
|
56
|
+
<Editable edit={config.messageEdit?.('jobs_newsletterNudge')} value={msg.jobs_newsletterNudge()}>
|
|
57
|
+
{#snippet children(text, attrs)}<p class="nudge" {...attrs}>{text}</p>{/snippet}
|
|
58
|
+
</Editable>
|
|
53
59
|
{/if}
|
|
54
60
|
|
|
55
61
|
<style>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getUiConfig } from '../../config/context.js';
|
|
3
3
|
import type { SiteLink } from '../../config/types.js';
|
|
4
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
5
|
+
import Editable from '../../edit/Editable.svelte';
|
|
6
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
4
7
|
import Link from '../ui/Link.svelte';
|
|
5
8
|
|
|
6
9
|
/**
|
|
@@ -10,9 +13,11 @@
|
|
|
10
13
|
*/
|
|
11
14
|
interface Props {
|
|
12
15
|
links: SiteLink[];
|
|
16
|
+
/** Edit descriptors for the link labels — see Nav. */
|
|
17
|
+
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
let { links }: Props = $props();
|
|
20
|
+
let { links, editFor = undefined }: Props = $props();
|
|
16
21
|
|
|
17
22
|
const config = getUiConfig();
|
|
18
23
|
|
|
@@ -26,10 +31,22 @@
|
|
|
26
31
|
</span>
|
|
27
32
|
<ul>
|
|
28
33
|
{#each links as link (link.href)}
|
|
29
|
-
<li
|
|
34
|
+
<li>
|
|
35
|
+
<ActionLabel edit={editFor?.(link)} value={link.label}>
|
|
36
|
+
{#snippet control()}<Link href={link.href}>{link.label}</Link>{/snippet}
|
|
37
|
+
</ActionLabel>
|
|
38
|
+
</li>
|
|
30
39
|
{/each}
|
|
31
40
|
</ul>
|
|
32
|
-
<p class="rights"
|
|
41
|
+
<p class="rights">
|
|
42
|
+
© {year}
|
|
43
|
+
<Editable
|
|
44
|
+
edit={config.messageEdit?.('footer_rights')}
|
|
45
|
+
value={config.messages.footer_rights()}
|
|
46
|
+
>
|
|
47
|
+
{#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
|
|
48
|
+
</Editable>
|
|
49
|
+
</p>
|
|
33
50
|
</div>
|
|
34
51
|
</footer>
|
|
35
52
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SiteLink } from '../../config/types.js';
|
|
2
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
2
3
|
/**
|
|
3
4
|
* The links are a prop rather than a package constant: the host app owns
|
|
4
5
|
* its routes, and Nav and Footer render the same list — the foundation
|
|
@@ -6,6 +7,8 @@ import type { SiteLink } from '../../config/types.js';
|
|
|
6
7
|
*/
|
|
7
8
|
interface Props {
|
|
8
9
|
links: SiteLink[];
|
|
10
|
+
/** Edit descriptors for the link labels — see Nav. */
|
|
11
|
+
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
9
12
|
}
|
|
10
13
|
declare const Footer: import("svelte").Component<Props, {}, "">;
|
|
11
14
|
type Footer = ReturnType<typeof Footer>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getUiConfig } from '../../config/context.js';
|
|
3
3
|
import type { SiteLink } from '../../config/types.js';
|
|
4
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
5
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
4
6
|
import { isPathUnder } from '../../utils/paths.js';
|
|
5
7
|
import GhostButton from '../ui/GhostButton.svelte';
|
|
6
8
|
import Link from '../ui/Link.svelte';
|
|
@@ -16,9 +18,16 @@
|
|
|
16
18
|
account?: { displayName: string } | null;
|
|
17
19
|
/** The URL highlighting reads from. Only tests and stories pass one. */
|
|
18
20
|
url?: URL;
|
|
21
|
+
/**
|
|
22
|
+
* Edit descriptors for the link LABELS — a function because the host
|
|
23
|
+
* owns both the routes and the message keys behind them. While the
|
|
24
|
+
* adapter is editing, a link with a descriptor renders as editable
|
|
25
|
+
* text instead of an anchor (see ActionLabel).
|
|
26
|
+
*/
|
|
27
|
+
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
let { links, account = null, url = undefined }: Props = $props();
|
|
30
|
+
let { links, account = null, url = undefined, editFor = undefined }: Props = $props();
|
|
22
31
|
|
|
23
32
|
const config = getUiConfig();
|
|
24
33
|
const msg = $derived(config.messages);
|
|
@@ -77,9 +86,13 @@
|
|
|
77
86
|
<ul class="links">
|
|
78
87
|
{#each links as link (link.href)}
|
|
79
88
|
<li>
|
|
80
|
-
<
|
|
81
|
-
{
|
|
82
|
-
|
|
89
|
+
<ActionLabel edit={editFor?.(link)} value={link.label}>
|
|
90
|
+
{#snippet control()}
|
|
91
|
+
<Link href={link.href} aria-current={isCurrent(link.href) ? 'page' : undefined}>
|
|
92
|
+
{link.label}
|
|
93
|
+
</Link>
|
|
94
|
+
{/snippet}
|
|
95
|
+
</ActionLabel>
|
|
83
96
|
</li>
|
|
84
97
|
{/each}
|
|
85
98
|
</ul>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SiteLink } from '../../config/types.js';
|
|
2
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
/**
|
|
4
5
|
* Primary navigation entries. A prop rather than a package constant:
|
|
@@ -12,6 +13,13 @@ interface Props {
|
|
|
12
13
|
} | null;
|
|
13
14
|
/** The URL highlighting reads from. Only tests and stories pass one. */
|
|
14
15
|
url?: URL;
|
|
16
|
+
/**
|
|
17
|
+
* Edit descriptors for the link LABELS — a function because the host
|
|
18
|
+
* owns both the routes and the message keys behind them. While the
|
|
19
|
+
* adapter is editing, a link with a descriptor renders as editable
|
|
20
|
+
* text instead of an anchor (see ActionLabel).
|
|
21
|
+
*/
|
|
22
|
+
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
15
23
|
}
|
|
16
24
|
declare const Nav: import("svelte").Component<Props, {}, "">;
|
|
17
25
|
type Nav = ReturnType<typeof Nav>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getUiConfig } from '../../config/context.js';
|
|
3
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
3
4
|
import Link from './Link.svelte';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -37,21 +38,32 @@
|
|
|
37
38
|
|
|
38
39
|
{#if pages > 1 || isOutOfRange}
|
|
39
40
|
<nav class="pagination" aria-label={msg.pagination_label()}>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
<ActionLabel
|
|
42
|
+
edit={config.messageEdit?.('pagination_previous')}
|
|
43
|
+
value={msg.pagination_previous()}
|
|
44
|
+
>
|
|
45
|
+
{#snippet control()}
|
|
46
|
+
{#if hasPrevious}
|
|
47
|
+
<Link href={href(previousPage)} rel="prev">{msg.pagination_previous()}</Link>
|
|
48
|
+
{:else}
|
|
49
|
+
<span class="disabled">{msg.pagination_previous()}</span>
|
|
50
|
+
{/if}
|
|
51
|
+
{/snippet}
|
|
52
|
+
</ActionLabel>
|
|
45
53
|
|
|
46
54
|
<p class="status" aria-live="polite">
|
|
47
55
|
{msg.pagination_status({ page, pages })}
|
|
48
56
|
</p>
|
|
49
57
|
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
<ActionLabel edit={config.messageEdit?.('pagination_next')} value={msg.pagination_next()}>
|
|
59
|
+
{#snippet control()}
|
|
60
|
+
{#if hasNext}
|
|
61
|
+
<Link href={href(page + 1)} rel="next">{msg.pagination_next()}</Link>
|
|
62
|
+
{:else}
|
|
63
|
+
<span class="disabled">{msg.pagination_next()}</span>
|
|
64
|
+
{/if}
|
|
65
|
+
{/snippet}
|
|
66
|
+
</ActionLabel>
|
|
55
67
|
</nav>
|
|
56
68
|
{/if}
|
|
57
69
|
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
<script lang="ts">
|
|
12
12
|
import { getUiConfig } from '../../config/context.js';
|
|
13
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
14
|
+
import Editable from '../../edit/Editable.svelte';
|
|
13
15
|
import { COMMENT_BODY } from '../../forms/constraints.js';
|
|
14
16
|
import type { CommentData, CommentThreadData } from '../../content/types.js';
|
|
15
17
|
import Button from '../ui/Button.svelte';
|
|
@@ -67,10 +69,16 @@
|
|
|
67
69
|
{/snippet}
|
|
68
70
|
|
|
69
71
|
<section aria-labelledby="comments-heading">
|
|
70
|
-
<
|
|
72
|
+
<Editable edit={config.messageEdit?.('comments_title')} value={msg.comments_title()}>
|
|
73
|
+
{#snippet children(text, attrs)}
|
|
74
|
+
<h2 class="subsection-heading" id="comments-heading" {...attrs}>{text}</h2>
|
|
75
|
+
{/snippet}
|
|
76
|
+
</Editable>
|
|
71
77
|
|
|
72
78
|
{#if comments.length === 0}
|
|
73
|
-
<
|
|
79
|
+
<Editable edit={config.messageEdit?.('comments_empty')} value={msg.comments_empty()}>
|
|
80
|
+
{#snippet children(text, attrs)}<p class="empty" {...attrs}>{text}</p>{/snippet}
|
|
81
|
+
</Editable>
|
|
74
82
|
{:else}
|
|
75
83
|
<ul class="list">
|
|
76
84
|
{#each comments as thread (thread.id)}
|
|
@@ -111,9 +119,16 @@
|
|
|
111
119
|
<input {...reply.fields.weeklySlug.as('hidden', weeklySlug)} />
|
|
112
120
|
<input {...reply.fields.parentId.as('hidden', String(thread.id))} />
|
|
113
121
|
<div class="actions">
|
|
114
|
-
<
|
|
115
|
-
{
|
|
116
|
-
|
|
122
|
+
<ActionLabel
|
|
123
|
+
edit={config.messageEdit?.('comments_replySubmit')}
|
|
124
|
+
value={msg.comments_replySubmit()}
|
|
125
|
+
>
|
|
126
|
+
{#snippet control()}
|
|
127
|
+
<Button type="submit" pending={reply.pending}>
|
|
128
|
+
{msg.comments_replySubmit()}
|
|
129
|
+
</Button>
|
|
130
|
+
{/snippet}
|
|
131
|
+
</ActionLabel>
|
|
117
132
|
</div>
|
|
118
133
|
<FormErrorFeedback result={reply.result} messages={errorMessages} />
|
|
119
134
|
</form>
|
|
@@ -141,14 +156,23 @@
|
|
|
141
156
|
<input {...main.fields.weeklySlug.as('hidden', weeklySlug)} />
|
|
142
157
|
|
|
143
158
|
<div class="actions">
|
|
144
|
-
<
|
|
159
|
+
<ActionLabel edit={config.messageEdit?.('comments_submit')} value={msg.comments_submit()}>
|
|
160
|
+
{#snippet control()}
|
|
161
|
+
<Button type="submit" pending={main.pending}>{msg.comments_submit()}</Button>
|
|
162
|
+
{/snippet}
|
|
163
|
+
</ActionLabel>
|
|
145
164
|
</div>
|
|
146
165
|
|
|
147
166
|
<FormErrorFeedback result={main.result} messages={errorMessages} />
|
|
148
167
|
</form>
|
|
149
168
|
{:else}
|
|
150
169
|
<p class="login-prompt">
|
|
151
|
-
|
|
170
|
+
<Editable
|
|
171
|
+
edit={config.messageEdit?.('comments_loginPrompt')}
|
|
172
|
+
value={msg.comments_loginPrompt()}
|
|
173
|
+
>
|
|
174
|
+
{#snippet children(text, attrs)}<span {...attrs}>{text}</span>{/snippet}
|
|
175
|
+
</Editable>
|
|
152
176
|
<Link href="/login">{msg.comments_loginLink()}</Link>
|
|
153
177
|
·
|
|
154
178
|
<Link href="/signup">{msg.comments_signupLink()}</Link>
|
package/dist/config/context.js
CHANGED
|
@@ -37,7 +37,10 @@ export function setUiConfig(input) {
|
|
|
37
37
|
},
|
|
38
38
|
get messages() {
|
|
39
39
|
return input().messages ?? defaultMessages;
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
|
+
// Undefined means inert: components render their message strings as
|
|
42
|
+
// plain text, which is every app but the CMS.
|
|
43
|
+
messageEdit: (key) => input().messageEdit?.(key)
|
|
41
44
|
};
|
|
42
45
|
return setContext(KEY, resolved);
|
|
43
46
|
}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EditDescriptor } from '../edit/types.js';
|
|
1
2
|
/**
|
|
2
3
|
* The site's locales and their canonical member. Fixed rather than generic:
|
|
3
4
|
* `ca` is load-bearing — it is the required key of every localized column
|
|
@@ -156,6 +157,14 @@ export interface UiConfig {
|
|
|
156
157
|
/** Appended to document titles and shown as the wordmark. */
|
|
157
158
|
siteName: string;
|
|
158
159
|
messages: UiMessages;
|
|
160
|
+
/**
|
|
161
|
+
* Edit descriptor for one of THIS config's message strings, by catalog
|
|
162
|
+
* key — the interface-wording half of edit mode. Optional and undefined by
|
|
163
|
+
* default, so a read-only app (and every story/test) renders the messages
|
|
164
|
+
* as plain text. A CMS-shaped host supplies it alongside an EditAdapter;
|
|
165
|
+
* components offer only their parameterless, plain-text message sites.
|
|
166
|
+
*/
|
|
167
|
+
messageEdit?: (key: string) => EditDescriptor | undefined;
|
|
159
168
|
}
|
|
160
169
|
/** What an app hands UiProvider: any subset; the rest keeps package defaults. */
|
|
161
170
|
export interface UiConfigInput {
|
|
@@ -166,4 +175,5 @@ export interface UiConfigInput {
|
|
|
166
175
|
canonicalPathname?: UiConfig['canonicalPathname'];
|
|
167
176
|
siteName?: string;
|
|
168
177
|
messages?: UiMessages;
|
|
178
|
+
messageEdit?: UiConfig['messageEdit'];
|
|
169
179
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { getEditAdapter } from './context.js';
|
|
4
|
+
import Editable from './Editable.svelte';
|
|
5
|
+
import type { EditDescriptor } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The label of an INTERACTIVE control — a button, a nav link — made
|
|
9
|
+
* editable the way WeeklieCard's title is: while the adapter is editing
|
|
10
|
+
* and a descriptor is supplied, the control is REPLACED by its label as
|
|
11
|
+
* editable text, because a caret inside a live control would activate it
|
|
12
|
+
* (a link navigates mid-draft, a label focuses its input). The control
|
|
13
|
+
* comes back the moment editing turns off; with no descriptor or no
|
|
14
|
+
* adapter it renders untouched, byte-identical to a read-only build.
|
|
15
|
+
*/
|
|
16
|
+
interface Props {
|
|
17
|
+
/** Undefined leaves the control alone — the inert default. */
|
|
18
|
+
edit: EditDescriptor | undefined;
|
|
19
|
+
/** The label text the control renders. */
|
|
20
|
+
value: string;
|
|
21
|
+
/** The real control, rendered whenever the label is not being edited. */
|
|
22
|
+
control: Snippet;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let { edit, value, control }: Props = $props();
|
|
26
|
+
|
|
27
|
+
const adapter = getEditAdapter();
|
|
28
|
+
const editing = $derived(edit !== undefined && (adapter?.isEditing ?? false));
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
{#if editing}
|
|
32
|
+
<Editable {edit} {value}>
|
|
33
|
+
{#snippet children(text, attrs)}<span class="action-label" {...attrs}>{text}</span>{/snippet}
|
|
34
|
+
</Editable>
|
|
35
|
+
{:else}
|
|
36
|
+
{@render control()}
|
|
37
|
+
{/if}
|
|
38
|
+
|
|
39
|
+
<style>
|
|
40
|
+
/* Roughly the control's footprint, so the swap doesn't collapse the row. */
|
|
41
|
+
.action-label {
|
|
42
|
+
display: inline-block;
|
|
43
|
+
padding: 0.2em 0.4em;
|
|
44
|
+
}
|
|
45
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { EditDescriptor } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The label of an INTERACTIVE control — a button, a nav link — made
|
|
5
|
+
* editable the way WeeklieCard's title is: while the adapter is editing
|
|
6
|
+
* and a descriptor is supplied, the control is REPLACED by its label as
|
|
7
|
+
* editable text, because a caret inside a live control would activate it
|
|
8
|
+
* (a link navigates mid-draft, a label focuses its input). The control
|
|
9
|
+
* comes back the moment editing turns off; with no descriptor or no
|
|
10
|
+
* adapter it renders untouched, byte-identical to a read-only build.
|
|
11
|
+
*/
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Undefined leaves the control alone — the inert default. */
|
|
14
|
+
edit: EditDescriptor | undefined;
|
|
15
|
+
/** The label text the control renders. */
|
|
16
|
+
value: string;
|
|
17
|
+
/** The real control, rendered whenever the label is not being edited. */
|
|
18
|
+
control: Snippet;
|
|
19
|
+
}
|
|
20
|
+
declare const ActionLabel: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type ActionLabel = ReturnType<typeof ActionLabel>;
|
|
22
|
+
export default ActionLabel;
|
package/dist/edit/helpers.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { Locale } from '../config/types.js';
|
|
2
2
|
import type { EditableEntity, EditDescriptor } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Descriptor for one interface-wording message (a Paraglide catalog key).
|
|
5
|
+
* Only parameterless messages are sensibly editable in place — editing the
|
|
6
|
+
* RENDERED text of a parameterized one would overwrite its template.
|
|
7
|
+
*/
|
|
8
|
+
export declare function chromeEdit(key: string, locale: Locale, options?: {
|
|
9
|
+
format?: EditDescriptor['format'];
|
|
10
|
+
label?: string;
|
|
11
|
+
}): EditDescriptor;
|
|
3
12
|
/** Descriptor for one page-copy block (`page_content` row). */
|
|
4
13
|
export declare function pageCopyEdit(page: string, sectionKey: string, locale: Locale, options?: {
|
|
5
14
|
format?: EditDescriptor['format'];
|
package/dist/edit/helpers.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Descriptor for one interface-wording message (a Paraglide catalog key).
|
|
3
|
+
* Only parameterless messages are sensibly editable in place — editing the
|
|
4
|
+
* RENDERED text of a parameterized one would overwrite its template.
|
|
5
|
+
*/
|
|
6
|
+
export function chromeEdit(key, locale, options) {
|
|
7
|
+
return { ref: { kind: 'chrome', key }, locale, ...options };
|
|
8
|
+
}
|
|
1
9
|
/** Descriptor for one page-copy block (`page_content` row). */
|
|
2
10
|
export function pageCopyEdit(page, sectionKey, locale, options) {
|
|
3
11
|
return { ref: { kind: 'page-copy', page, sectionKey }, locale, ...options };
|
package/dist/edit/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { getEditAdapter, setEditAdapter } from './context.js';
|
|
2
|
+
export { default as ActionLabel } from './ActionLabel.svelte';
|
|
2
3
|
export { default as Editable } from './Editable.svelte';
|
|
3
|
-
export { entityEdit, pageCopyEdit } from './helpers.js';
|
|
4
|
+
export { chromeEdit, entityEdit, pageCopyEdit } from './helpers.js';
|
|
4
5
|
export { localize } from './types.js';
|
|
5
6
|
export type { ContentRef, EditableEntity, EditAdapter, EditDescriptor, LocalizedText } from './types.js';
|
package/dist/edit/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { getEditAdapter, setEditAdapter } from './context.js';
|
|
2
|
+
export { default as ActionLabel } from './ActionLabel.svelte';
|
|
2
3
|
export { default as Editable } from './Editable.svelte';
|
|
3
|
-
export { entityEdit, pageCopyEdit } from './helpers.js';
|
|
4
|
+
export { chromeEdit, entityEdit, pageCopyEdit } from './helpers.js';
|
|
4
5
|
export { localize } from './types.js';
|
package/dist/edit/types.d.ts
CHANGED
|
@@ -17,8 +17,12 @@ export declare function localize(value: LocalizedText, locale: Locale): string;
|
|
|
17
17
|
*/
|
|
18
18
|
export type EditableEntity = 'weeklies' | 'projects' | 'milestones' | 'team_members' | 'job_openings';
|
|
19
19
|
/**
|
|
20
|
-
* The identity of one editable string in the content model:
|
|
21
|
-
*
|
|
20
|
+
* The identity of one editable string in the content model: a page copy
|
|
21
|
+
* block (`page_content` row), one localized field of a domain entity, or one
|
|
22
|
+
* interface-wording message (`chrome`) — a Paraglide catalog key. What a
|
|
23
|
+
* chrome save MEANS is the host's decision, like every other ref: the
|
|
24
|
+
* foundation CMS writes the website's `ui_messages` table, from which the
|
|
25
|
+
* catalogs are regenerated at deploy.
|
|
22
26
|
*/
|
|
23
27
|
export type ContentRef = {
|
|
24
28
|
kind: 'page-copy';
|
|
@@ -29,6 +33,9 @@ export type ContentRef = {
|
|
|
29
33
|
entity: EditableEntity;
|
|
30
34
|
id: string | number;
|
|
31
35
|
field: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'chrome';
|
|
38
|
+
key: string;
|
|
32
39
|
};
|
|
33
40
|
/** What a component hands the adapter alongside the new value. */
|
|
34
41
|
export interface EditDescriptor {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vit-foundation/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"svelte"
|
|
123
123
|
],
|
|
124
124
|
"license": "MIT",
|
|
125
|
-
"description": "
|
|
125
|
+
"description": "Fundació VIT's shared Svelte 5 component library with pluggable edit mode",
|
|
126
126
|
"repository": {
|
|
127
127
|
"type": "git",
|
|
128
128
|
"url": "git+https://github.com/fndvit/VizVit-UI.git"
|