@glw907/cairn-cms 0.56.2 → 0.57.1
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/CHANGELOG.md +134 -0
- package/dist/components/AdminLayout.svelte +3 -0
- package/dist/components/CairnAdmin.svelte +8 -1
- package/dist/components/CairnAdmin.svelte.d.ts +2 -0
- package/dist/components/CairnMediaLibrary.svelte +949 -0
- package/dist/components/CairnMediaLibrary.svelte.d.ts +37 -0
- package/dist/components/EditPage.svelte +348 -7
- package/dist/components/EditPage.svelte.d.ts +2 -0
- package/dist/components/MarkdownEditor.svelte +283 -1
- package/dist/components/MarkdownEditor.svelte.d.ts +37 -1
- package/dist/components/MediaCaptureCard.svelte +135 -0
- package/dist/components/MediaCaptureCard.svelte.d.ts +40 -0
- package/dist/components/MediaFigureControl.svelte +247 -0
- package/dist/components/MediaFigureControl.svelte.d.ts +40 -0
- package/dist/components/MediaHeroField.svelte +578 -0
- package/dist/components/MediaHeroField.svelte.d.ts +75 -0
- package/dist/components/MediaInsertPopover.svelte +449 -0
- package/dist/components/MediaInsertPopover.svelte.d.ts +58 -0
- package/dist/components/MediaPicker.svelte +257 -0
- package/dist/components/MediaPicker.svelte.d.ts +41 -0
- package/dist/components/admin-icons.d.ts +12 -0
- package/dist/components/admin-icons.js +12 -0
- package/dist/components/cairn-admin.css +901 -9
- package/dist/components/client-ingest.d.ts +142 -0
- package/dist/components/client-ingest.js +297 -0
- package/dist/components/editor-media.d.ts +11 -0
- package/dist/components/editor-media.js +206 -0
- package/dist/components/editor-placeholder.d.ts +26 -0
- package/dist/components/editor-placeholder.js +166 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/markdown-directives.d.ts +12 -0
- package/dist/components/markdown-directives.js +42 -0
- package/dist/components/markdown-format.d.ts +89 -0
- package/dist/components/markdown-format.js +255 -0
- package/dist/components/media-upload-outcome.d.ts +52 -0
- package/dist/components/media-upload-outcome.js +48 -0
- package/dist/content/compose.js +3 -0
- package/dist/content/frontmatter.js +22 -0
- package/dist/content/manifest.d.ts +4 -0
- package/dist/content/manifest.js +41 -1
- package/dist/content/media-refs.d.ts +7 -0
- package/dist/content/media-refs.js +52 -0
- package/dist/content/schema.d.ts +5 -2
- package/dist/content/schema.js +17 -0
- package/dist/content/types.d.ts +64 -11
- package/dist/content/validate.js +31 -0
- package/dist/delivery/public-routes.d.ts +16 -0
- package/dist/delivery/public-routes.js +46 -3
- package/dist/delivery/seo-fields.js +7 -1
- package/dist/delivery/seo.d.ts +2 -0
- package/dist/delivery/seo.js +3 -0
- package/dist/doctor/checks-local.d.ts +1 -0
- package/dist/doctor/checks-local.js +21 -0
- package/dist/doctor/index.d.ts +3 -1
- package/dist/doctor/index.js +11 -2
- package/dist/doctor/types.d.ts +3 -0
- package/dist/doctor/wrangler-config.d.ts +3 -0
- package/dist/doctor/wrangler-config.js +20 -0
- package/dist/env.d.ts +19 -0
- package/dist/env.js +26 -0
- package/dist/index.d.ts +1 -1
- package/dist/log/events.d.ts +1 -1
- package/dist/media/config.d.ts +24 -0
- package/dist/media/config.js +69 -0
- package/dist/media/delivery-bucket.d.ts +34 -0
- package/dist/media/delivery-bucket.js +10 -0
- package/dist/media/index.d.ts +6 -0
- package/dist/media/index.js +13 -0
- package/dist/media/library-entry.d.ts +30 -0
- package/dist/media/library-entry.js +17 -0
- package/dist/media/manifest.d.ts +44 -0
- package/dist/media/manifest.js +105 -0
- package/dist/media/naming.d.ts +18 -0
- package/dist/media/naming.js +112 -0
- package/dist/media/reconcile.d.ts +36 -0
- package/dist/media/reconcile.js +45 -0
- package/dist/media/reference.d.ts +12 -0
- package/dist/media/reference.js +33 -0
- package/dist/media/sniff.d.ts +18 -0
- package/dist/media/sniff.js +106 -0
- package/dist/media/store.d.ts +25 -0
- package/dist/media/store.js +16 -0
- package/dist/media/transform-url.d.ts +26 -0
- package/dist/media/transform-url.js +38 -0
- package/dist/media/usage.d.ts +48 -0
- package/dist/media/usage.js +90 -0
- package/dist/render/pipeline.d.ts +2 -0
- package/dist/render/pipeline.js +13 -2
- package/dist/render/registry.js +3 -0
- package/dist/render/remark-figure.d.ts +4 -0
- package/dist/render/remark-figure.js +103 -0
- package/dist/render/resolve-media.d.ts +34 -0
- package/dist/render/resolve-media.js +78 -0
- package/dist/render/sanitize-schema.d.ts +4 -2
- package/dist/render/sanitize-schema.js +5 -3
- package/dist/sveltekit/admin-dispatch.d.ts +2 -0
- package/dist/sveltekit/admin-dispatch.js +5 -0
- package/dist/sveltekit/cairn-admin.d.ts +8 -1
- package/dist/sveltekit/cairn-admin.js +10 -2
- package/dist/sveltekit/content-routes.d.ts +77 -2
- package/dist/sveltekit/content-routes.js +470 -10
- package/dist/sveltekit/csrf.d.ts +16 -0
- package/dist/sveltekit/csrf.js +18 -0
- package/dist/sveltekit/guard.js +10 -3
- package/dist/sveltekit/index.d.ts +2 -1
- package/dist/sveltekit/index.js +1 -0
- package/dist/sveltekit/media-route.d.ts +12 -0
- package/dist/sveltekit/media-route.js +137 -0
- package/dist/vite/index.d.ts +3 -0
- package/dist/vite/index.js +7 -2
- package/package.json +7 -1
- package/src/lib/components/AdminLayout.svelte +3 -0
- package/src/lib/components/CairnAdmin.svelte +8 -1
- package/src/lib/components/CairnMediaLibrary.svelte +949 -0
- package/src/lib/components/EditPage.svelte +348 -7
- package/src/lib/components/MarkdownEditor.svelte +283 -1
- package/src/lib/components/MediaCaptureCard.svelte +135 -0
- package/src/lib/components/MediaFigureControl.svelte +247 -0
- package/src/lib/components/MediaHeroField.svelte +578 -0
- package/src/lib/components/MediaInsertPopover.svelte +449 -0
- package/src/lib/components/MediaPicker.svelte +257 -0
- package/src/lib/components/admin-icons.ts +12 -0
- package/src/lib/components/cairn-admin.css +37 -0
- package/src/lib/components/client-ingest.ts +380 -0
- package/src/lib/components/editor-media.ts +248 -0
- package/src/lib/components/editor-placeholder.ts +213 -0
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/markdown-directives.ts +46 -0
- package/src/lib/components/markdown-format.ts +307 -1
- package/src/lib/components/media-upload-outcome.ts +83 -0
- package/src/lib/content/compose.ts +3 -0
- package/src/lib/content/frontmatter.ts +20 -1
- package/src/lib/content/manifest.ts +44 -1
- package/src/lib/content/media-refs.ts +58 -0
- package/src/lib/content/schema.ts +31 -7
- package/src/lib/content/types.ts +80 -13
- package/src/lib/content/validate.ts +29 -1
- package/src/lib/delivery/public-routes.ts +52 -3
- package/src/lib/delivery/seo-fields.ts +6 -1
- package/src/lib/delivery/seo.ts +5 -0
- package/src/lib/doctor/checks-local.ts +22 -0
- package/src/lib/doctor/index.ts +21 -3
- package/src/lib/doctor/types.ts +3 -0
- package/src/lib/doctor/wrangler-config.ts +23 -0
- package/src/lib/env.ts +28 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/log/events.ts +8 -1
- package/src/lib/media/config.ts +103 -0
- package/src/lib/media/delivery-bucket.ts +41 -0
- package/src/lib/media/index.ts +22 -0
- package/src/lib/media/library-entry.ts +58 -0
- package/src/lib/media/manifest.ts +122 -0
- package/src/lib/media/naming.ts +130 -0
- package/src/lib/media/reconcile.ts +79 -0
- package/src/lib/media/reference.ts +40 -0
- package/src/lib/media/sniff.ts +114 -0
- package/src/lib/media/store.ts +57 -0
- package/src/lib/media/transform-url.ts +58 -0
- package/src/lib/media/usage.ts +152 -0
- package/src/lib/render/pipeline.ts +17 -3
- package/src/lib/render/registry.ts +5 -0
- package/src/lib/render/remark-figure.ts +132 -0
- package/src/lib/render/resolve-media.ts +96 -0
- package/src/lib/render/sanitize-schema.ts +5 -3
- package/src/lib/sveltekit/admin-dispatch.ts +6 -1
- package/src/lib/sveltekit/cairn-admin.ts +13 -3
- package/src/lib/sveltekit/content-routes.ts +589 -12
- package/src/lib/sveltekit/csrf.ts +18 -0
- package/src/lib/sveltekit/guard.ts +12 -3
- package/src/lib/sveltekit/index.ts +6 -0
- package/src/lib/sveltekit/media-route.ts +158 -0
- package/src/lib/vite/index.ts +9 -2
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
The figure control form: the caption and placement an author gives an inline media image. The host
|
|
4
|
+
(EditPage) opens it over the media image at the caret, in `wrap` mode for a bare image or `edit` mode
|
|
5
|
+
for an existing `:::figure`. It is the form CONTENT only; the host mounts it inside the Edit-block
|
|
6
|
+
dialog. On submit it emits the chosen caption and role through `onapply`; in edit mode it also offers
|
|
7
|
+
`onunwrap` to strip the figure back to the bare image.
|
|
8
|
+
|
|
9
|
+
The caption is the visible line under the image, distinct from the alt text. The control surfaces the
|
|
10
|
+
image's alt state (Described or Needs alt) that the host derives and passes; the deep needs-alt wiring
|
|
11
|
+
is the host's. When the image is decorative AND the author gives it a caption, the control warns: a
|
|
12
|
+
decorative image is hidden from screen readers, so a visible caption on it is a contradiction.
|
|
13
|
+
|
|
14
|
+
The placement is a roving-tabindex radiogroup (Measure, Center, Wide, Full): one bordered group, the
|
|
15
|
+
active segment tinted with a check glyph (the non-color state cue, WCAG 1.4.1), arrow keys move and
|
|
16
|
+
select. Measure maps to the null role (the measure default, no role brace); the others map to their
|
|
17
|
+
own name.
|
|
18
|
+
-->
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import { untrack } from 'svelte';
|
|
21
|
+
import type { FigureRole } from './markdown-format.js';
|
|
22
|
+
|
|
23
|
+
/** The roles the segmented control offers, with Measure standing for the null (measure-default)
|
|
24
|
+
* role. Declared as a const tuple so the segment loop and the keyboard handler share one source. */
|
|
25
|
+
const ROLE_OPTIONS: { value: FigureRole | null; label: string }[] = [
|
|
26
|
+
{ value: null, label: 'Measure' },
|
|
27
|
+
{ value: 'center', label: 'Center' },
|
|
28
|
+
{ value: 'wide', label: 'Wide' },
|
|
29
|
+
{ value: 'full', label: 'Full' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
interface Props {
|
|
33
|
+
/** The initial caption; the field seeds from it and the author edits a local copy. */
|
|
34
|
+
caption?: string;
|
|
35
|
+
/** The initial placement role, or null for the measure default. */
|
|
36
|
+
role?: FigureRole | null;
|
|
37
|
+
/** `wrap` for a bare image (the primary action wraps it), `edit` for an existing figure (the
|
|
38
|
+
* primary action updates it and a ghost action unwraps it). */
|
|
39
|
+
mode?: 'wrap' | 'edit';
|
|
40
|
+
/** Whether the image's alt is empty or marked decorative; the host derives it. Drives the
|
|
41
|
+
* alt-status row and the decorative-plus-caption warning. */
|
|
42
|
+
decorative?: boolean;
|
|
43
|
+
/** Emit the chosen caption and role: the host wraps (wrap mode) or updates (edit mode). */
|
|
44
|
+
onapply: (choice: { caption: string; role: FigureRole | null }) => void;
|
|
45
|
+
/** Emit the unwrap action (edit mode only); the host replaces the figure with its bare image. */
|
|
46
|
+
onunwrap?: () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let { caption = '', role = null, mode = 'wrap', decorative = false, onapply, onunwrap }: Props =
|
|
50
|
+
$props();
|
|
51
|
+
|
|
52
|
+
// The author's working copies, seeded once from the props the control opened with. untrack marks
|
|
53
|
+
// the read a deliberate one-time seed (the control mounts fresh per image), not a reactive miss.
|
|
54
|
+
let captionValue = $state(untrack(() => caption));
|
|
55
|
+
let roleValue = $state<FigureRole | null>(untrack(() => role));
|
|
56
|
+
|
|
57
|
+
// The index of the active role in ROLE_OPTIONS, the roving-tabindex focus target.
|
|
58
|
+
const activeIndex = $derived(ROLE_OPTIONS.findIndex((o) => o.value === roleValue));
|
|
59
|
+
|
|
60
|
+
// The decorative-plus-caption contradiction: a decorative image is hidden from screen readers, so a
|
|
61
|
+
// visible caption on it is a state to flag (never blocked, surfaced for the author to resolve).
|
|
62
|
+
const decorativeWithCaption = $derived(decorative && captionValue.trim() !== '');
|
|
63
|
+
|
|
64
|
+
// The segment refs, so arrow-key navigation can move focus to the newly selected segment.
|
|
65
|
+
let segmentEls = $state<HTMLButtonElement[]>([]);
|
|
66
|
+
|
|
67
|
+
function pickRole(value: FigureRole | null) {
|
|
68
|
+
roleValue = value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Arrow keys move and select within the radiogroup (the roving-tabindex pattern); Home/End jump to
|
|
72
|
+
// the ends. Selection follows focus, the standard radiogroup behavior.
|
|
73
|
+
function onSegmentKeydown(e: KeyboardEvent, index: number) {
|
|
74
|
+
let next = index;
|
|
75
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') next = (index + 1) % ROLE_OPTIONS.length;
|
|
76
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp')
|
|
77
|
+
next = (index - 1 + ROLE_OPTIONS.length) % ROLE_OPTIONS.length;
|
|
78
|
+
else if (e.key === 'Home') next = 0;
|
|
79
|
+
else if (e.key === 'End') next = ROLE_OPTIONS.length - 1;
|
|
80
|
+
else return;
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
pickRole(ROLE_OPTIONS[next].value);
|
|
83
|
+
segmentEls[next]?.focus();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function submit(e: SubmitEvent) {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
onapply({ caption: captionValue.trim(), role: roleValue });
|
|
89
|
+
}
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<form class="flex flex-col gap-4" onsubmit={submit}>
|
|
93
|
+
<div class="flex flex-col gap-1">
|
|
94
|
+
<label for="cairn-figure-caption" class="text-sm font-medium">Caption</label>
|
|
95
|
+
<input
|
|
96
|
+
id="cairn-figure-caption"
|
|
97
|
+
class="input w-full"
|
|
98
|
+
type="text"
|
|
99
|
+
placeholder="Describe what the image adds to the post"
|
|
100
|
+
aria-describedby="cairn-figure-caption-hint"
|
|
101
|
+
bind:value={captionValue}
|
|
102
|
+
/>
|
|
103
|
+
<p id="cairn-figure-caption-hint" class="text-xs text-[var(--color-muted)]">
|
|
104
|
+
Shown under the image, for everyone. This is not the alt text.
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<!-- The alt-status row: the image's alt state the host derives. Described or Needs alt, the latter
|
|
109
|
+
in the warning ink with a glyph so the state never rides hue alone (WCAG 1.4.1). -->
|
|
110
|
+
<div class="flex items-center gap-2 text-sm">
|
|
111
|
+
<span class="font-medium" aria-hidden="true">Alt text</span>
|
|
112
|
+
{#if decorative}
|
|
113
|
+
<span
|
|
114
|
+
class="inline-flex items-center gap-1 font-medium text-[var(--cairn-warning-ink)]"
|
|
115
|
+
data-cairn-alt-status="needs"
|
|
116
|
+
aria-label="Alt text: needs a description"
|
|
117
|
+
>
|
|
118
|
+
<svg
|
|
119
|
+
width="13"
|
|
120
|
+
height="13"
|
|
121
|
+
viewBox="0 0 24 24"
|
|
122
|
+
fill="none"
|
|
123
|
+
stroke="currentColor"
|
|
124
|
+
stroke-width="2.4"
|
|
125
|
+
stroke-linecap="round"
|
|
126
|
+
stroke-linejoin="round"
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
>
|
|
129
|
+
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
130
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
131
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
132
|
+
</svg>
|
|
133
|
+
Needs alt
|
|
134
|
+
</span>
|
|
135
|
+
{:else}
|
|
136
|
+
<span
|
|
137
|
+
class="inline-flex items-center gap-1 text-[var(--color-muted)]"
|
|
138
|
+
data-cairn-alt-status="described"
|
|
139
|
+
aria-label="Alt text: described"
|
|
140
|
+
>
|
|
141
|
+
<svg
|
|
142
|
+
width="13"
|
|
143
|
+
height="13"
|
|
144
|
+
viewBox="0 0 24 24"
|
|
145
|
+
fill="none"
|
|
146
|
+
stroke="currentColor"
|
|
147
|
+
stroke-width="3"
|
|
148
|
+
stroke-linecap="round"
|
|
149
|
+
stroke-linejoin="round"
|
|
150
|
+
aria-hidden="true"
|
|
151
|
+
>
|
|
152
|
+
<path d="M20 6 9 17l-5-5" />
|
|
153
|
+
</svg>
|
|
154
|
+
Described
|
|
155
|
+
</span>
|
|
156
|
+
{/if}
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="flex flex-col gap-1">
|
|
160
|
+
<span id="cairn-figure-placement-label" class="text-sm font-medium">Placement</span>
|
|
161
|
+
<!-- The segmented control: one bordered group, borderless segments, the active one tinted with a
|
|
162
|
+
check glyph. A roving-tabindex radiogroup, so arrow keys move and select and one Tab stop
|
|
163
|
+
reaches the group. -->
|
|
164
|
+
<div
|
|
165
|
+
role="radiogroup"
|
|
166
|
+
aria-labelledby="cairn-figure-placement-label"
|
|
167
|
+
class="bg-base-100 inline-flex items-center self-start overflow-hidden rounded-lg border border-[var(--cairn-card-border)]"
|
|
168
|
+
>
|
|
169
|
+
{#each ROLE_OPTIONS as option, index (option.label)}
|
|
170
|
+
{@const pressed = roleValue === option.value}
|
|
171
|
+
<button
|
|
172
|
+
bind:this={segmentEls[index]}
|
|
173
|
+
type="button"
|
|
174
|
+
role="radio"
|
|
175
|
+
aria-checked={pressed}
|
|
176
|
+
tabindex={index === activeIndex ? 0 : -1}
|
|
177
|
+
class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-normal {index > 0
|
|
178
|
+
? 'border-l border-[var(--cairn-card-border)]'
|
|
179
|
+
: ''} {pressed ? 'bg-primary/10 text-primary font-medium' : 'text-[var(--color-muted)]'}"
|
|
180
|
+
onclick={() => pickRole(option.value)}
|
|
181
|
+
onkeydown={(e) => onSegmentKeydown(e, index)}
|
|
182
|
+
>
|
|
183
|
+
{#if pressed}
|
|
184
|
+
<svg
|
|
185
|
+
class="h-3 w-3"
|
|
186
|
+
viewBox="0 0 24 24"
|
|
187
|
+
fill="none"
|
|
188
|
+
stroke="currentColor"
|
|
189
|
+
stroke-width="2.5"
|
|
190
|
+
stroke-linecap="round"
|
|
191
|
+
stroke-linejoin="round"
|
|
192
|
+
aria-hidden="true"
|
|
193
|
+
>
|
|
194
|
+
<path d="M20 6 9 17l-5-5" />
|
|
195
|
+
</svg>
|
|
196
|
+
{/if}
|
|
197
|
+
{option.label}
|
|
198
|
+
</button>
|
|
199
|
+
{/each}
|
|
200
|
+
</div>
|
|
201
|
+
<p class="text-xs text-[var(--color-muted)]">
|
|
202
|
+
Center suits an image narrower than the text column. Measure keeps it at the column width.
|
|
203
|
+
</p>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<!-- An always-present polite live region, so the contradiction announces when it appears in
|
|
207
|
+
response to the author's own typing (not only on the dialog's open read). Empty and invisible
|
|
208
|
+
until the warning fills it. -->
|
|
209
|
+
<div role="status" aria-live="polite">
|
|
210
|
+
{#if decorativeWithCaption}
|
|
211
|
+
<div
|
|
212
|
+
class="flex items-start gap-2 rounded-[0.55rem] p-2.5 text-[var(--cairn-warning-ink)]"
|
|
213
|
+
style="background: color-mix(in oklab, var(--cairn-warning-ink) 8%, transparent);"
|
|
214
|
+
>
|
|
215
|
+
<svg
|
|
216
|
+
width="14"
|
|
217
|
+
height="14"
|
|
218
|
+
viewBox="0 0 24 24"
|
|
219
|
+
fill="none"
|
|
220
|
+
stroke="currentColor"
|
|
221
|
+
stroke-width="2.2"
|
|
222
|
+
stroke-linecap="round"
|
|
223
|
+
stroke-linejoin="round"
|
|
224
|
+
class="mt-0.5 flex-none"
|
|
225
|
+
aria-hidden="true"
|
|
226
|
+
>
|
|
227
|
+
<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
228
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
229
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
230
|
+
</svg>
|
|
231
|
+
<p class="m-0 text-xs leading-relaxed">
|
|
232
|
+
A decorative image is hidden from screen readers, but this one has a caption. Describe it, or
|
|
233
|
+
remove the caption.
|
|
234
|
+
</p>
|
|
235
|
+
</div>
|
|
236
|
+
{/if}
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<div class="flex justify-end gap-2">
|
|
240
|
+
{#if mode === 'edit'}
|
|
241
|
+
<button type="button" class="btn btn-sm btn-ghost" onclick={() => onunwrap?.()}>Unwrap</button>
|
|
242
|
+
<button type="submit" class="btn btn-sm btn-primary">Update figure</button>
|
|
243
|
+
{:else}
|
|
244
|
+
<button type="submit" class="btn btn-sm btn-primary">Wrap in figure</button>
|
|
245
|
+
{/if}
|
|
246
|
+
</div>
|
|
247
|
+
</form>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FigureRole } from './markdown-format.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** The initial caption; the field seeds from it and the author edits a local copy. */
|
|
4
|
+
caption?: string;
|
|
5
|
+
/** The initial placement role, or null for the measure default. */
|
|
6
|
+
role?: FigureRole | null;
|
|
7
|
+
/** `wrap` for a bare image (the primary action wraps it), `edit` for an existing figure (the
|
|
8
|
+
* primary action updates it and a ghost action unwraps it). */
|
|
9
|
+
mode?: 'wrap' | 'edit';
|
|
10
|
+
/** Whether the image's alt is empty or marked decorative; the host derives it. Drives the
|
|
11
|
+
* alt-status row and the decorative-plus-caption warning. */
|
|
12
|
+
decorative?: boolean;
|
|
13
|
+
/** Emit the chosen caption and role: the host wraps (wrap mode) or updates (edit mode). */
|
|
14
|
+
onapply: (choice: {
|
|
15
|
+
caption: string;
|
|
16
|
+
role: FigureRole | null;
|
|
17
|
+
}) => void;
|
|
18
|
+
/** Emit the unwrap action (edit mode only); the host replaces the figure with its bare image. */
|
|
19
|
+
onunwrap?: () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The figure control form: the caption and placement an author gives an inline media image. The host
|
|
23
|
+
* (EditPage) opens it over the media image at the caret, in `wrap` mode for a bare image or `edit` mode
|
|
24
|
+
* for an existing `:::figure`. It is the form CONTENT only; the host mounts it inside the Edit-block
|
|
25
|
+
* dialog. On submit it emits the chosen caption and role through `onapply`; in edit mode it also offers
|
|
26
|
+
* `onunwrap` to strip the figure back to the bare image.
|
|
27
|
+
*
|
|
28
|
+
* The caption is the visible line under the image, distinct from the alt text. The control surfaces the
|
|
29
|
+
* image's alt state (Described or Needs alt) that the host derives and passes; the deep needs-alt wiring
|
|
30
|
+
* is the host's. When the image is decorative AND the author gives it a caption, the control warns: a
|
|
31
|
+
* decorative image is hidden from screen readers, so a visible caption on it is a contradiction.
|
|
32
|
+
*
|
|
33
|
+
* The placement is a roving-tabindex radiogroup (Measure, Center, Wide, Full): one bordered group, the
|
|
34
|
+
* active segment tinted with a check glyph (the non-color state cue, WCAG 1.4.1), arrow keys move and
|
|
35
|
+
* select. Measure maps to the null role (the measure default, no role brace); the others map to their
|
|
36
|
+
* own name.
|
|
37
|
+
*/
|
|
38
|
+
declare const MediaFigureControl: import("svelte").Component<Props, {}, "">;
|
|
39
|
+
type MediaFigureControl = ReturnType<typeof MediaFigureControl>;
|
|
40
|
+
export default MediaFigureControl;
|