@vit-foundation/ui 0.5.0 → 0.9.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 +11 -3
- package/dist/components/contact/ContactForm.svelte +10 -1
- package/dist/components/layout/Footer.svelte +10 -2
- 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/projects/ProjectCard.svelte +11 -3
- package/dist/components/timeline/TimelineMilestone.svelte +21 -6
- package/dist/components/ui/Field.svelte +23 -2
- package/dist/components/ui/Field.svelte.d.ts +8 -0
- package/dist/components/ui/FilterChips.svelte +21 -8
- package/dist/components/ui/FilterChips.svelte.d.ts +8 -0
- package/dist/components/ui/Pagination.svelte +22 -10
- package/dist/components/weeklies/CommentSection.svelte +16 -4
- package/dist/components/weeklies/SortSelect.svelte +12 -7
- package/dist/edit/ActionLabel.svelte +45 -0
- package/dist/edit/ActionLabel.svelte.d.ts +22 -0
- package/dist/edit/index.d.ts +1 -0
- package/dist/edit/index.js +1 -0
- package/dist/edit/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -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 Editable from '../../edit/Editable.svelte';
|
|
4
5
|
import { withNewsletterIntent } from '../../forms/transport.js';
|
|
5
6
|
import type { NewsletterToggleFormInstance } from './AccountPanel.svelte';
|
|
@@ -61,9 +62,16 @@
|
|
|
61
62
|
<form {...f}>
|
|
62
63
|
<input {...f.fields.action.as('hidden', 'subscribe')} />
|
|
63
64
|
<LocaleField field={f.fields.locale} />
|
|
64
|
-
<
|
|
65
|
-
{
|
|
66
|
-
|
|
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>
|
|
67
75
|
</form>
|
|
68
76
|
{#if f.result}
|
|
69
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';
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
<Field
|
|
58
59
|
id="contact-category"
|
|
59
60
|
label={msg.contact_categoryLabel()}
|
|
61
|
+
labelEdit={config.messageEdit?.('contact_categoryLabel')}
|
|
60
62
|
field={f.fields.category}
|
|
61
63
|
constraint={null}
|
|
62
64
|
>
|
|
@@ -72,6 +74,7 @@
|
|
|
72
74
|
<Field
|
|
73
75
|
id="contact-name"
|
|
74
76
|
label={msg.contact_nameLabel()}
|
|
77
|
+
labelEdit={config.messageEdit?.('contact_nameLabel')}
|
|
75
78
|
field={f.fields.name}
|
|
76
79
|
constraint={CONTACT_NAME}
|
|
77
80
|
>
|
|
@@ -89,6 +92,7 @@
|
|
|
89
92
|
<Field
|
|
90
93
|
id="contact-email"
|
|
91
94
|
label={msg.contact_emailLabel()}
|
|
95
|
+
labelEdit={config.messageEdit?.('contact_emailLabel')}
|
|
92
96
|
field={f.fields.email}
|
|
93
97
|
constraint={EMAIL}
|
|
94
98
|
>
|
|
@@ -106,6 +110,7 @@
|
|
|
106
110
|
<Field
|
|
107
111
|
id="contact-message"
|
|
108
112
|
label={msg.contact_messageLabel()}
|
|
113
|
+
labelEdit={config.messageEdit?.('contact_messageLabel')}
|
|
109
114
|
field={f.fields.message}
|
|
110
115
|
constraint={CONTACT_MESSAGE}
|
|
111
116
|
>
|
|
@@ -120,7 +125,11 @@
|
|
|
120
125
|
<Honeypot form={f.fields} id="contact-website" />
|
|
121
126
|
|
|
122
127
|
<div class="actions">
|
|
123
|
-
<
|
|
128
|
+
<ActionLabel edit={config.messageEdit?.('contact_submit')} value={msg.contact_submit()}>
|
|
129
|
+
{#snippet control()}
|
|
130
|
+
<Button type="submit" pending={f.pending}>{msg.contact_submit()}</Button>
|
|
131
|
+
{/snippet}
|
|
132
|
+
</ActionLabel>
|
|
124
133
|
</div>
|
|
125
134
|
|
|
126
135
|
{#if f.result && !f.result.ok}
|
|
@@ -1,7 +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';
|
|
4
5
|
import Editable from '../../edit/Editable.svelte';
|
|
6
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
5
7
|
import Link from '../ui/Link.svelte';
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -11,9 +13,11 @@
|
|
|
11
13
|
*/
|
|
12
14
|
interface Props {
|
|
13
15
|
links: SiteLink[];
|
|
16
|
+
/** Edit descriptors for the link labels — see Nav. */
|
|
17
|
+
editFor?: (link: SiteLink) => EditDescriptor | undefined;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
|
-
let { links }: Props = $props();
|
|
20
|
+
let { links, editFor = undefined }: Props = $props();
|
|
17
21
|
|
|
18
22
|
const config = getUiConfig();
|
|
19
23
|
|
|
@@ -27,7 +31,11 @@
|
|
|
27
31
|
</span>
|
|
28
32
|
<ul>
|
|
29
33
|
{#each links as link (link.href)}
|
|
30
|
-
<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>
|
|
31
39
|
{/each}
|
|
32
40
|
</ul>
|
|
33
41
|
<p class="rights">
|
|
@@ -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>;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<script lang="ts">
|
|
12
12
|
import { getUiConfig } from '../../config/context.js';
|
|
13
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
13
14
|
import type { ProjectCardData } from '../../content/types.js';
|
|
14
15
|
import { getEditAdapter } from '../../edit/context.js';
|
|
15
16
|
import Editable from '../../edit/Editable.svelte';
|
|
@@ -57,9 +58,16 @@
|
|
|
57
58
|
{#snippet children(text, attrs)}<p {...attrs}>{text}</p>{/snippet}
|
|
58
59
|
</Editable>
|
|
59
60
|
{#if project.hasStory}
|
|
60
|
-
<
|
|
61
|
-
{config.
|
|
62
|
-
|
|
61
|
+
<ActionLabel
|
|
62
|
+
edit={config.messageEdit?.('common_readMore')}
|
|
63
|
+
value={config.messages.common_readMore()}
|
|
64
|
+
>
|
|
65
|
+
{#snippet control()}
|
|
66
|
+
<Link href={`/what-we-do/${project.slug}`} class="more">
|
|
67
|
+
{config.messages.common_readMore()}
|
|
68
|
+
</Link>
|
|
69
|
+
{/snippet}
|
|
70
|
+
</ActionLabel>
|
|
63
71
|
{/if}
|
|
64
72
|
</div>
|
|
65
73
|
</article>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<script lang="ts">
|
|
12
12
|
import { getUiConfig } from '../../config/context.js';
|
|
13
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
13
14
|
import type { MilestoneData } from '../../content/types.js';
|
|
14
15
|
import Editable from '../../edit/Editable.svelte';
|
|
15
16
|
import { MILESTONE_CATEGORY_COLOR, milestoneCategoryLabel } from '../../utils/milestones.js';
|
|
@@ -47,7 +48,12 @@
|
|
|
47
48
|
|
|
48
49
|
<article style={`--milestone-color: ${color}`}>
|
|
49
50
|
<span class="dot" aria-hidden="true"></span>
|
|
50
|
-
<
|
|
51
|
+
<Editable
|
|
52
|
+
edit={config.messageEdit?.(`category_${milestone.category}`)}
|
|
53
|
+
value={milestoneCategoryLabel(milestone.category, config.messages)}
|
|
54
|
+
>
|
|
55
|
+
{#snippet children(text, attrs)}<p class="category" {...attrs}>{text}</p>{/snippet}
|
|
56
|
+
</Editable>
|
|
51
57
|
<DateText value={milestone.occurredOn} />
|
|
52
58
|
<Editable edit={edit?.title} value={milestone.title}>
|
|
53
59
|
{#snippet children(text, attrs)}<h3 {...attrs}>{text}</h3>{/snippet}
|
|
@@ -62,11 +68,20 @@
|
|
|
62
68
|
{/if}
|
|
63
69
|
{#if milestone.linkUrl}
|
|
64
70
|
<p class="more">
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
<ActionLabel
|
|
72
|
+
edit={config.messageEdit?.('common_readMore')}
|
|
73
|
+
value={config.messages.common_readMore()}
|
|
74
|
+
>
|
|
75
|
+
{#snippet control()}
|
|
76
|
+
{#if isExternal}
|
|
77
|
+
<a href={milestone.linkUrl} rel="external noopener"
|
|
78
|
+
>{config.messages.common_readMore()}</a
|
|
79
|
+
>
|
|
80
|
+
{:else if milestone.linkUrl}
|
|
81
|
+
<Link href={milestone.linkUrl}>{config.messages.common_readMore()}</Link>
|
|
82
|
+
{/if}
|
|
83
|
+
{/snippet}
|
|
84
|
+
</ActionLabel>
|
|
70
85
|
</p>
|
|
71
86
|
{/if}
|
|
72
87
|
</article>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { FieldConstraint } from '../../content/types.js';
|
|
4
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
5
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
4
6
|
|
|
5
7
|
/** Attributes the control must spread (`{...attrs}`) to stay wired. */
|
|
6
8
|
interface ControlAttrs {
|
|
@@ -46,18 +48,37 @@
|
|
|
46
48
|
constraint: FieldConstraint | null;
|
|
47
49
|
/** Render the label visually hidden (surrounding context already labels it). */
|
|
48
50
|
hideLabel?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Edit descriptor for the LABEL's wording. While editing, only the
|
|
53
|
+
* label swaps for editable text — an editable span is not a <label>,
|
|
54
|
+
* so clicking it no longer focuses the control, and the control itself
|
|
55
|
+
* stays rendered and live. Hidden labels are never offered.
|
|
56
|
+
*/
|
|
57
|
+
labelEdit?: EditDescriptor;
|
|
49
58
|
/** The input/textarea/select, spreading the given attrs. */
|
|
50
59
|
children: Snippet<[ControlAttrs]>;
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
let {
|
|
62
|
+
let {
|
|
63
|
+
id,
|
|
64
|
+
label,
|
|
65
|
+
field,
|
|
66
|
+
constraint,
|
|
67
|
+
hideLabel = false,
|
|
68
|
+
labelEdit = undefined,
|
|
69
|
+
children
|
|
70
|
+
}: Props = $props();
|
|
54
71
|
|
|
55
72
|
const errorMessage = $derived(field.issues()?.[0]?.message);
|
|
56
73
|
const showError = $derived(errorMessage !== undefined);
|
|
57
74
|
</script>
|
|
58
75
|
|
|
59
76
|
<div class="field">
|
|
60
|
-
<
|
|
77
|
+
<ActionLabel edit={hideLabel ? undefined : labelEdit} value={label}>
|
|
78
|
+
{#snippet control()}
|
|
79
|
+
<label class:visually-hidden={hideLabel} for={id}>{label}</label>
|
|
80
|
+
{/snippet}
|
|
81
|
+
</ActionLabel>
|
|
61
82
|
{@render children({
|
|
62
83
|
id,
|
|
63
84
|
'aria-describedby': showError ? `${id}-error` : undefined,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { FieldConstraint } from '../../content/types.js';
|
|
3
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
3
4
|
/** Attributes the control must spread (`{...attrs}`) to stay wired. */
|
|
4
5
|
interface ControlAttrs {
|
|
5
6
|
id: string;
|
|
@@ -47,6 +48,13 @@ interface Props {
|
|
|
47
48
|
constraint: FieldConstraint | null;
|
|
48
49
|
/** Render the label visually hidden (surrounding context already labels it). */
|
|
49
50
|
hideLabel?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Edit descriptor for the LABEL's wording. While editing, only the
|
|
53
|
+
* label swaps for editable text — an editable span is not a <label>,
|
|
54
|
+
* so clicking it no longer focuses the control, and the control itself
|
|
55
|
+
* stays rendered and live. Hidden labels are never offered.
|
|
56
|
+
*/
|
|
57
|
+
labelEdit?: EditDescriptor;
|
|
50
58
|
/** The input/textarea/select, spreading the given attrs. */
|
|
51
59
|
children: Snippet<[ControlAttrs]>;
|
|
52
60
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
3
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
2
4
|
import GhostButton from './GhostButton.svelte';
|
|
3
5
|
|
|
4
6
|
interface Chip {
|
|
@@ -13,9 +15,16 @@
|
|
|
13
15
|
label: string;
|
|
14
16
|
/** Called with the toggled value, or null when the active chip is unselected. */
|
|
15
17
|
onchange: (value: string | null) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Edit descriptors for the chip LABELS — a function because the host
|
|
20
|
+
* owns what a chip's wording is (a theme's localized name, a category
|
|
21
|
+
* message). While editing, a chip with a descriptor renders as
|
|
22
|
+
* editable text instead of a button (see ActionLabel).
|
|
23
|
+
*/
|
|
24
|
+
editFor?: (chip: Chip) => EditDescriptor | undefined;
|
|
16
25
|
}
|
|
17
26
|
|
|
18
|
-
let { chips, selected, label, onchange }: Props = $props();
|
|
27
|
+
let { chips, selected, label, onchange, editFor = undefined }: Props = $props();
|
|
19
28
|
|
|
20
29
|
function toggle(value: string): void {
|
|
21
30
|
onchange(selected === value ? null : value);
|
|
@@ -24,13 +33,17 @@
|
|
|
24
33
|
|
|
25
34
|
<div class="chips" role="group" aria-label={label}>
|
|
26
35
|
{#each chips as chip (chip.value)}
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
<ActionLabel edit={editFor?.(chip)} value={chip.label}>
|
|
37
|
+
{#snippet control()}
|
|
38
|
+
<GhostButton
|
|
39
|
+
variant="chip"
|
|
40
|
+
aria-pressed={selected === chip.value}
|
|
41
|
+
onclick={() => toggle(chip.value)}
|
|
42
|
+
>
|
|
43
|
+
{chip.label}
|
|
44
|
+
</GhostButton>
|
|
45
|
+
{/snippet}
|
|
46
|
+
</ActionLabel>
|
|
34
47
|
{/each}
|
|
35
48
|
</div>
|
|
36
49
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EditDescriptor } from '../../edit/types.js';
|
|
1
2
|
interface Chip {
|
|
2
3
|
value: string;
|
|
3
4
|
label: string;
|
|
@@ -9,6 +10,13 @@ interface Props {
|
|
|
9
10
|
label: string;
|
|
10
11
|
/** Called with the toggled value, or null when the active chip is unselected. */
|
|
11
12
|
onchange: (value: string | null) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Edit descriptors for the chip LABELS — a function because the host
|
|
15
|
+
* owns what a chip's wording is (a theme's localized name, a category
|
|
16
|
+
* message). While editing, a chip with a descriptor renders as
|
|
17
|
+
* editable text instead of a button (see ActionLabel).
|
|
18
|
+
*/
|
|
19
|
+
editFor?: (chip: Chip) => EditDescriptor | undefined;
|
|
12
20
|
}
|
|
13
21
|
declare const FilterChips: import("svelte").Component<Props, {}, "">;
|
|
14
22
|
type FilterChips = ReturnType<typeof FilterChips>;
|
|
@@ -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,7 @@
|
|
|
10
10
|
|
|
11
11
|
<script lang="ts">
|
|
12
12
|
import { getUiConfig } from '../../config/context.js';
|
|
13
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
13
14
|
import Editable from '../../edit/Editable.svelte';
|
|
14
15
|
import { COMMENT_BODY } from '../../forms/constraints.js';
|
|
15
16
|
import type { CommentData, CommentThreadData } from '../../content/types.js';
|
|
@@ -118,9 +119,16 @@
|
|
|
118
119
|
<input {...reply.fields.weeklySlug.as('hidden', weeklySlug)} />
|
|
119
120
|
<input {...reply.fields.parentId.as('hidden', String(thread.id))} />
|
|
120
121
|
<div class="actions">
|
|
121
|
-
<
|
|
122
|
-
{
|
|
123
|
-
|
|
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>
|
|
124
132
|
</div>
|
|
125
133
|
<FormErrorFeedback result={reply.result} messages={errorMessages} />
|
|
126
134
|
</form>
|
|
@@ -148,7 +156,11 @@
|
|
|
148
156
|
<input {...main.fields.weeklySlug.as('hidden', weeklySlug)} />
|
|
149
157
|
|
|
150
158
|
<div class="actions">
|
|
151
|
-
<
|
|
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>
|
|
152
164
|
</div>
|
|
153
165
|
|
|
154
166
|
<FormErrorFeedback result={main.result} messages={errorMessages} />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getUiConfig } from '../../config/context.js';
|
|
3
3
|
import type { SortDirection } from '../../content/types.js';
|
|
4
|
+
import ActionLabel from '../../edit/ActionLabel.svelte';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
value: SortDirection;
|
|
@@ -18,13 +19,17 @@
|
|
|
18
19
|
}
|
|
19
20
|
</script>
|
|
20
21
|
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
</
|
|
22
|
+
<ActionLabel edit={config.messageEdit?.('weeklies_sortLabel')} value={msg.weeklies_sortLabel()}>
|
|
23
|
+
{#snippet control()}
|
|
24
|
+
<label>
|
|
25
|
+
<span>{msg.weeklies_sortLabel()}</span>
|
|
26
|
+
<select class="control" {value} onchange={handleChange}>
|
|
27
|
+
<option value="desc">{msg.weeklies_sortDesc()}</option>
|
|
28
|
+
<option value="asc">{msg.weeklies_sortAsc()}</option>
|
|
29
|
+
</select>
|
|
30
|
+
</label>
|
|
31
|
+
{/snippet}
|
|
32
|
+
</ActionLabel>
|
|
28
33
|
|
|
29
34
|
<style>
|
|
30
35
|
label {
|
|
@@ -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/index.d.ts
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
4
|
export { chromeEdit, entityEdit, pageCopyEdit } from './helpers.js';
|
|
4
5
|
export { localize } 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
4
|
export { chromeEdit, entityEdit, pageCopyEdit } from './helpers.js';
|
|
4
5
|
export { localize } from './types.js';
|
package/dist/edit/types.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare function localize(value: LocalizedText, locale: Locale): string;
|
|
|
15
15
|
* The entities whose fields the components can mark editable. Names match
|
|
16
16
|
* the content tables so an adapter can route a save without a mapping layer.
|
|
17
17
|
*/
|
|
18
|
-
export type EditableEntity = 'weeklies' | 'projects' | 'milestones' | 'team_members' | 'job_openings';
|
|
18
|
+
export type EditableEntity = 'weeklies' | 'projects' | 'milestones' | 'team_members' | 'job_openings' | 'themes';
|
|
19
19
|
/**
|
|
20
20
|
* The identity of one editable string in the content model: a page copy
|
|
21
21
|
* block (`page_content` row), one localized field of a domain entity, or one
|