@openreachtech/furo-vue 1.5.0 → 1.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/README.ja.md +2 -0
- package/README.md +2 -0
- package/docs/design-tokens.md +419 -0
- package/docs/furo-accordion.md +109 -0
- package/docs/furo-alert-dialog.md +107 -0
- package/docs/furo-attachment.md +133 -0
- package/docs/furo-autocomplete-field.md +719 -0
- package/docs/furo-avatar-group.md +87 -0
- package/docs/furo-avatar.md +101 -0
- package/docs/furo-badge.md +126 -0
- package/docs/furo-breadcrumb.md +89 -0
- package/docs/furo-button.md +263 -0
- package/docs/furo-checkbox.md +384 -0
- package/docs/furo-collapsible.md +104 -0
- package/docs/furo-control-block.md +102 -0
- package/docs/furo-date-picker.md +270 -0
- package/docs/furo-date-time-picker.md +110 -0
- package/docs/furo-dialog.md +88 -0
- package/docs/furo-drawer.md +122 -0
- package/docs/furo-dropdown-menu.md +298 -0
- package/docs/furo-editable-field.md +391 -0
- package/docs/furo-editor.md +304 -0
- package/docs/furo-email-field.md +93 -0
- package/docs/furo-empty-state.md +97 -0
- package/docs/furo-error-state.md +97 -0
- package/docs/furo-file-field.md +163 -0
- package/docs/furo-icon.md +288 -0
- package/docs/furo-number-field.md +397 -0
- package/docs/furo-pagination.md +101 -0
- package/docs/furo-password-field.md +75 -0
- package/docs/furo-popover.md +114 -0
- package/docs/furo-radio.md +125 -0
- package/docs/furo-scroll-area.md +112 -0
- package/docs/furo-search-field.md +94 -0
- package/docs/furo-section-header.md +135 -0
- package/docs/furo-select.md +443 -0
- package/docs/furo-separator.md +87 -0
- package/docs/furo-sidebar.md +178 -0
- package/docs/furo-skeleton.md +92 -0
- package/docs/furo-splitter.md +103 -0
- package/docs/furo-stepper.md +127 -0
- package/docs/furo-switch.md +107 -0
- package/docs/furo-table.md +233 -0
- package/docs/furo-tabs.md +173 -0
- package/docs/furo-text-field.md +249 -0
- package/docs/furo-textarea.md +214 -0
- package/docs/furo-time-field.md +276 -0
- package/docs/furo-toast.md +217 -0
- package/docs/furo-toggle-group.md +123 -0
- package/docs/furo-toggle.md +104 -0
- package/docs/furo-tool-bar.md +88 -0
- package/docs/furo-tooltip.md +107 -0
- package/lib/assets/css/furo/0030.variables-semantic-color.css +15 -1
- package/lib/components/atoms/FuroButton/FuroButtonContext.js +8 -8
- package/lib/components/atoms/FuroCheckbox/FuroCheckboxContext.js +1 -1
- package/lib/components/atoms/FuroIcon/FuroIcon.vue +1 -1
- package/lib/components/atoms/FuroSwitch/FuroSwitchContext.js +1 -1
- package/lib/components/atoms/FuroToggle/FuroToggleContext.js +1 -1
- package/lib/components/molecules/FuroAccordion/FuroAccordionContext.js +1 -1
- package/lib/components/molecules/FuroAttachment/FuroAttachment.vue +1 -1
- package/lib/components/molecules/FuroBreadcrumb/FuroBreadcrumb.vue +1 -1
- package/lib/components/molecules/FuroCollapsible/FuroCollapsibleContext.js +1 -1
- package/lib/components/molecules/FuroControlBlock/FuroControlBlock.vue +2 -2
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenu.vue +1 -1
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.js +1 -2
- package/lib/components/molecules/FuroErrorState/FuroErrorState.vue +1 -1
- package/lib/components/molecules/FuroPopover/FuroPopoverContext.js +1 -1
- package/lib/components/molecules/FuroSearchField/FuroSearchField.vue +1 -1
- package/lib/components/molecules/FuroSelect/FuroSelect.vue +8 -6
- package/lib/components/molecules/FuroSelect/FuroSelectContext.js +35 -0
- package/lib/components/molecules/FuroSelect/FuroSelectOptionText.vue +40 -0
- package/lib/components/molecules/FuroSelect/FuroSelectOptionTextContext.js +275 -0
- package/lib/components/molecules/FuroSplitter/FuroSplitterContext.js +1 -1
- package/lib/components/molecules/FuroToggleGroup/FuroToggleGroupContext.js +1 -1
- package/lib/components/molecules/FuroTooltip/FuroTooltipContext.js +1 -1
- package/lib/components/organisms/FuroSidebar/FuroSidebar.vue +3 -3
- package/lib/components/organisms/FuroTable/FuroTable.vue +1 -1
- package/lib/components/organisms/FuroTable/FuroTableContext.js +3 -3
- package/lib/components/organisms/FuroToast/FuroToastContext.js +6 -2
- package/lib/components/organisms/FuroToast/ToastQueue.js +4 -4
- package/nuxt/index.js +9 -9
- package/package.json +23 -22
- package/public/furo-vue/components.json +1 -1
- package/types/generated/components/atoms/FuroButton/FuroButtonContext.d.ts +4 -4
- package/types/generated/components/molecules/FuroSelect/FuroSelect.vue.d.ts +23 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectContext.d.ts +14 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectOptionText.vue.d.ts +50 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectOptionTextContext.d.ts +141 -0
- package/types/generated/components/organisms/FuroTable/FuroTableContext.d.ts +2 -2
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# FuroEditor
|
|
2
|
+
|
|
3
|
+
Rich-text editor organism for chatter-style composition. Built on a headless
|
|
4
|
+
rich-text engine and composed from furo controls (`FuroToolBar`, `FuroButton`,
|
|
5
|
+
`FuroTooltip`, `FuroDropdownMenu`, `FuroDialog`, `FuroTextField`).
|
|
6
|
+
|
|
7
|
+
FuroEditor is domain-agnostic: it ships formatting, links, paste handling, and the
|
|
8
|
+
mention/attachment *seams*, but never touches GraphQL, network, or domain schemas.
|
|
9
|
+
Mentions are supplied through a search adapter; attachment UI is rendered by the
|
|
10
|
+
consumer through a slot.
|
|
11
|
+
|
|
12
|
+
## Props
|
|
13
|
+
|
|
14
|
+
| Prop | Type | Default | Description |
|
|
15
|
+
|------|------|---------|-------------|
|
|
16
|
+
| `parcel` | `object \| null` | `null` | Configuration + state (see below). |
|
|
17
|
+
|
|
18
|
+
## Parcel
|
|
19
|
+
|
|
20
|
+
| Key | Type | Default | Description |
|
|
21
|
+
|-----|------|---------|-------------|
|
|
22
|
+
| `value` | `string` | `''` | HTML content. Controlled — pair with `update:value`. |
|
|
23
|
+
| `placeholder` | `string` | `''` | Empty-state placeholder. |
|
|
24
|
+
| `readOnly` | `boolean` | `false` | Hides the toolbar and makes content non-editable (keeps the bordered surface). |
|
|
25
|
+
| `appearance` | `'editor' \| 'plain'` | `'editor'` | `'plain'` renders content as bare text — no toolbar, border, background, or padding. Implies display-only. |
|
|
26
|
+
| `autofocus` | `boolean \| 'start' \| 'end' \| 'all' \| number` | `false` | Where the caret lands when the editor is created. Read once, at creation (see Focus). |
|
|
27
|
+
| `contentAttributes` | `Record<string, string>` | `{}` | Attributes set on the writing area, for example `aria-label`. Read once, at creation (see Accessibility). |
|
|
28
|
+
| `toolbar` | `Array<Array<string>>` | default | Toolbar layout as groups of action names; separators sit between groups. See Custom toolbar. |
|
|
29
|
+
| `attachedFiles` | `Array<*>` | `[]` | Attachment records held for the consumer; shape is the consumer's own. |
|
|
30
|
+
| `labels` | `object` | built-in | Partial overrides for toolbar tooltips and dialog text (any default label can be replaced). |
|
|
31
|
+
| `mention` | `{ searchMentions }` | absent | Enables `@`-mentions when present (see Mentions). |
|
|
32
|
+
| `image` | `{ uploadImage }` | absent | Enables inline images when present (see Inline images). |
|
|
33
|
+
| `actionOverrides` | `Record<string, fn>` | `{}` | Override the click behavior of a built-in control by name (see Custom actions). |
|
|
34
|
+
|
|
35
|
+
`mention.searchMentions` is `({ query }) => Promise<Array<{ id, label }>>`. Supplying
|
|
36
|
+
it turns on the mention control and `@` trigger; omit it to disable mentions entirely.
|
|
37
|
+
|
|
38
|
+
## Events
|
|
39
|
+
|
|
40
|
+
| Event | Payload | When |
|
|
41
|
+
|-------|---------|------|
|
|
42
|
+
| `update:value` | `string` (HTML) | Content changed (v-model:value). |
|
|
43
|
+
| `change-value` | `{ value }` | Content changed. |
|
|
44
|
+
| `commit-value` | `{ value }` | Editor blurred. |
|
|
45
|
+
| `update:attachedFiles` | `Array<*>` | Attachment list changed (e.g. a removal). |
|
|
46
|
+
| `mention:add` | `{ id, label }` | A mention was inserted. |
|
|
47
|
+
| `files:add` | `{ files }` | Files were dropped or pasted that the editor does not insert itself (see Dropped and pasted files). |
|
|
48
|
+
| `image:error` | `{ file, error }` | An inline-image upload was refused; the rest of the batch continues. |
|
|
49
|
+
|
|
50
|
+
## Slots
|
|
51
|
+
|
|
52
|
+
| Slot | Props | Description |
|
|
53
|
+
|------|-------|-------------|
|
|
54
|
+
| `toolbar-start` | `{ command, editor }` | Custom controls prepended to the toolbar. |
|
|
55
|
+
| `toolbar-end` | `{ command, editor, hiddenEntries }` | Custom controls appended to the toolbar. `hiddenEntries` carries the render descriptors for the actions no group named, for drawing a custom overflow menu. |
|
|
56
|
+
| `attachments` | `{ attachedFiles, remove, isEditable }` | Render the attachment UI. `remove` is `({ index }) => void`. |
|
|
57
|
+
| `footer` | — | Content rendered below the editor (e.g. submit row). |
|
|
58
|
+
|
|
59
|
+
## Toolbar
|
|
60
|
+
|
|
61
|
+
Formatting controls: text-style dropdown (normal / headings 1–4), text-color dropdown,
|
|
62
|
+
alignment dropdown (left / center / right / justify), and toggles for bold, italic,
|
|
63
|
+
underline, strikethrough, bullet list, numbered list, blockquote, link, and (when
|
|
64
|
+
enabled) mention. Active state is reflected per control.
|
|
65
|
+
|
|
66
|
+
## Content typography
|
|
67
|
+
|
|
68
|
+
The writing surface renders Body/Medium, and each heading renders a composite text
|
|
69
|
+
role at semibold weight, so a size never arrives paired with a line height the design
|
|
70
|
+
does not draw.
|
|
71
|
+
|
|
72
|
+
| Element | Role | Size | Line height |
|
|
73
|
+
|---------|------|------|-------------|
|
|
74
|
+
| Body text | `--font-body-medium` | `0.84375rem` | `1.25rem` |
|
|
75
|
+
| `h1` | `--font-headline-large` | `1.5rem` | `2rem` |
|
|
76
|
+
| `h2` | `--font-headline-medium` | `1.25rem` | `1.75rem` |
|
|
77
|
+
| `h3` | `--font-headline-small` | `1.125rem` | `1.75rem` |
|
|
78
|
+
| `h4` | `--font-label-medium` | `0.9375rem` | `1.5rem` |
|
|
79
|
+
|
|
80
|
+
Every value is `rem`, so the whole surface scales with the reader's root font size.
|
|
81
|
+
|
|
82
|
+
`h4` renders Label/Medium because the type ramp carries no `1rem` step; `0.9375rem` is
|
|
83
|
+
the step below the `1.125rem` its `h3` renders. Override any of these by redefining the
|
|
84
|
+
token, which re-themes every component that reads it, or by restyling the heading in
|
|
85
|
+
your own layer.
|
|
86
|
+
|
|
87
|
+
## Copy-paste behavior
|
|
88
|
+
|
|
89
|
+
Paste is handled in layers so external content lands cleanly:
|
|
90
|
+
|
|
91
|
+
1. **Plain-text normalization** — CRLF/CR collapse to LF; 3+ blank lines collapse to one.
|
|
92
|
+
2. **HTML sanitization** — pasted `font-family` / `font-size` declarations are stripped so external typography does not override the editor.
|
|
93
|
+
3. **Markdown auto-convert** — text that looks like Markdown is converted to HTML on paste.
|
|
94
|
+
4. **Newline preservation** — multi-line plain text keeps its empty lines (the default engine collapses runs of newlines), and Markdown links / bare URLs in pasted text become real links.
|
|
95
|
+
|
|
96
|
+
## Custom toolbar
|
|
97
|
+
|
|
98
|
+
`parcel.toolbar` defines the toolbar as an array of **groups**, each group an array
|
|
99
|
+
of action names. A vertical separator is rendered between groups, so reordering,
|
|
100
|
+
adding, removing, and re-grouping actions (and thus separators) is fully declarative.
|
|
101
|
+
|
|
102
|
+
Available names: `heading`, `color`, `align` (dropdowns); `bold`, `italic`,
|
|
103
|
+
`underline`, `strike`, `bulletList`, `orderedList`, `blockquote`, `link`, `image`,
|
|
104
|
+
`mention` (controls); `overflow` (menu). `mention` is dropped unless a mention
|
|
105
|
+
adapter is supplied, and `image` is dropped unless an image adapter is supplied.
|
|
106
|
+
|
|
107
|
+
Outside a production build, a name outside that vocabulary throws, naming the value
|
|
108
|
+
and listing what the toolbar accepts. A production build drops it instead. Watch the
|
|
109
|
+
two list controls in particular: they are named `bulletList` and `orderedList`, while
|
|
110
|
+
the labels that title them are keyed `bullets` and `numbers`.
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
// Bold/italic, then heading, then link/quote — three groups, two separators.
|
|
114
|
+
toolbar: [
|
|
115
|
+
['bold', 'italic'],
|
|
116
|
+
['heading'],
|
|
117
|
+
['link', 'blockquote'],
|
|
118
|
+
]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
When omitted, the default layout is:
|
|
122
|
+
`[['heading','color','align'], ['bold','italic','underline','strike'], ['bulletList','orderedList','blockquote'], ['link', 'mention'?]]`.
|
|
123
|
+
|
|
124
|
+
## Overflow
|
|
125
|
+
|
|
126
|
+
A narrow toolbar parks the rest of its actions behind `overflow`, which renders a `…`
|
|
127
|
+
menu holding every default action no group named. The commands, labels and active
|
|
128
|
+
states stay inside the component, so a compact toolbar costs one word:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
// Bold and italic stay on the bar; heading, colour, align, underline, strike,
|
|
132
|
+
// the two lists, quote and link move into the menu.
|
|
133
|
+
toolbar: [
|
|
134
|
+
['bold', 'italic'],
|
|
135
|
+
['overflow'],
|
|
136
|
+
]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A hidden dropdown becomes a submenu carrying its own options, and a hidden control
|
|
140
|
+
becomes a single row. Each row draws the same icon the on-bar control would, a
|
|
141
|
+
toggle row marks itself when its formatting is active, and colour rows keep their
|
|
142
|
+
swatch. When the groups already name every action, `overflow` renders nothing rather
|
|
143
|
+
than an empty menu. Override the `…` trigger's accessible name through the `overflow`
|
|
144
|
+
key of `parcel.labels`.
|
|
145
|
+
|
|
146
|
+
To draw the menu yourself instead, read `hiddenEntries` off the `toolbar-end` slot and
|
|
147
|
+
leave `overflow` out of the groups. Both paths expose the same descriptors:
|
|
148
|
+
|
|
149
|
+
```vue
|
|
150
|
+
<template #toolbar-end="{ hiddenEntries }">
|
|
151
|
+
<MyOverflowMenu :entries="hiddenEntries" />
|
|
152
|
+
</template>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Custom actions
|
|
156
|
+
|
|
157
|
+
Two seams let consumers extend or change the toolbar without forking the component:
|
|
158
|
+
|
|
159
|
+
- **Add controls** — render your own buttons in the `toolbar-start` / `toolbar-end`
|
|
160
|
+
slots. Each slot exposes `command` (the command sub-context: `toggleNodeOrMark`,
|
|
161
|
+
`executeDropdownCommand`, `insertText`, `applyLink`, `isActive`, …) and `editor`
|
|
162
|
+
(the raw engine instance), so a custom control can drive the editor directly.
|
|
163
|
+
- **Override built-ins** — `parcel.actionOverrides` maps a built-in control name
|
|
164
|
+
(`bold`, `italic`, `underline`, `strike`, `bulletList`, `orderedList`,
|
|
165
|
+
`blockquote`, `link`, `image`, `mention`) to `({ control, command, editor }) => void`.
|
|
166
|
+
When present, the override runs instead of the default behavior for that control.
|
|
167
|
+
|
|
168
|
+
```vue
|
|
169
|
+
<FuroEditor
|
|
170
|
+
:parcel="{
|
|
171
|
+
actionOverrides: {
|
|
172
|
+
link: ({ editor }) => myCustomLinkFlow(editor),
|
|
173
|
+
},
|
|
174
|
+
}"
|
|
175
|
+
>
|
|
176
|
+
<template #toolbar-end="{ command }">
|
|
177
|
+
<FuroButton :parcel="{ variant: 'ghost', size: 'sm' }" @click="command.insertText({ text: '✦ ' })">
|
|
178
|
+
Insert ✦
|
|
179
|
+
</FuroButton>
|
|
180
|
+
</template>
|
|
181
|
+
</FuroEditor>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Inline images
|
|
185
|
+
|
|
186
|
+
Supply `parcel.image.uploadImage` to enable embedding images **inside the content**
|
|
187
|
+
(distinct from `attachedFiles`, which render below the editor via the slot). The
|
|
188
|
+
adapter is `({ file }) => Promise<{ src, alt? }>` — the consumer uploads the file and
|
|
189
|
+
returns the URL to embed; the library never uploads anything itself.
|
|
190
|
+
|
|
191
|
+
When the adapter is present, an `image` toolbar control appears, and three triggers
|
|
192
|
+
insert inline images:
|
|
193
|
+
|
|
194
|
+
- **Toolbar button** — opens a native multi-image file picker.
|
|
195
|
+
- **Paste** — image files on the clipboard are uploaded and inserted at the cursor.
|
|
196
|
+
- **Drag & drop** — image files dropped onto the editor are inserted at the drop point.
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
image: {
|
|
200
|
+
uploadImage: async ({ file }) => {
|
|
201
|
+
const url = await myUploadService.put(file)
|
|
202
|
+
return { src: url, alt: file.name }
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Available names include `image` for `parcel.toolbar` ordering. Omit the adapter to
|
|
208
|
+
disable inline images entirely (the control and the paste/drop handlers stay off).
|
|
209
|
+
|
|
210
|
+
An adapter that rejects refuses that one file: the editor emits `image:error` with
|
|
211
|
+
`{ file, error }` and moves on to the next file in the batch, so four accepted pictures
|
|
212
|
+
still land when the fifth is turned away.
|
|
213
|
+
|
|
214
|
+
## Dropped and pasted files
|
|
215
|
+
|
|
216
|
+
Files the editor does not embed itself reach the consumer through `files:add`, carrying
|
|
217
|
+
`{ files }` as an array of `File`. That is every dropped or pasted file when no image
|
|
218
|
+
adapter is supplied, and every non-image file when one is. Images the editor uploads and
|
|
219
|
+
inserts inline never arrive this way, because it consumed them.
|
|
220
|
+
|
|
221
|
+
A drop that carries files is claimed by the editor, so the browser never navigates away
|
|
222
|
+
from the page. A paste is left alone, so pasted text and HTML still land as before.
|
|
223
|
+
|
|
224
|
+
```vue
|
|
225
|
+
<FuroEditor
|
|
226
|
+
:parcel="{ value: htmlRef }"
|
|
227
|
+
@files:add="attachFiles($event.files)"
|
|
228
|
+
/>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Focus
|
|
232
|
+
|
|
233
|
+
`parcel.autofocus` says where the caret lands when the editor is created: `true`,
|
|
234
|
+
`'start'`, `'end'`, `'all'`, a document position, or `false` (the default). It is read
|
|
235
|
+
once, while the editor is being built, so changing it later focuses nothing. To focus an
|
|
236
|
+
editor that already exists, call `focusChain()` on the command context.
|
|
237
|
+
|
|
238
|
+
## Accessibility
|
|
239
|
+
|
|
240
|
+
`parcel.contentAttributes` sets attributes on the writing area, which is how a consumer
|
|
241
|
+
gives it an accessible name:
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
contentAttributes: {
|
|
245
|
+
'aria-label': 'Write a comment',
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Like `placeholder`, it is read once while the editor is being built. Toolbar controls
|
|
250
|
+
name themselves from their labels, so every icon-only control and dropdown ships an
|
|
251
|
+
`aria-label` that `parcel.labels` overrides along with the tooltip text.
|
|
252
|
+
|
|
253
|
+
## Mentions
|
|
254
|
+
|
|
255
|
+
Supply `parcel.mention.searchMentions`. As the user types after `@`, the adapter is
|
|
256
|
+
queried with `{ query }` and returns `{ id, label }` items shown in a popup; selecting
|
|
257
|
+
one inserts a mention node and emits `mention:add`. The library performs no fetching —
|
|
258
|
+
the consumer's adapter owns the data source.
|
|
259
|
+
|
|
260
|
+
## Example
|
|
261
|
+
|
|
262
|
+
```vue
|
|
263
|
+
<FuroEditor
|
|
264
|
+
:parcel="{
|
|
265
|
+
value: htmlRef,
|
|
266
|
+
placeholder: 'Write something…',
|
|
267
|
+
mention: { searchMentions },
|
|
268
|
+
attachedFiles,
|
|
269
|
+
}"
|
|
270
|
+
@update:value="htmlRef = $event"
|
|
271
|
+
@update:attached-files="attachedFiles = $event"
|
|
272
|
+
@mention:add="onMentionAdd"
|
|
273
|
+
>
|
|
274
|
+
<template #attachments="{ attachedFiles, remove, isEditable }">
|
|
275
|
+
<!-- consumer-owned attachment UI -->
|
|
276
|
+
</template>
|
|
277
|
+
</FuroEditor>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Future enhancements (planned)
|
|
281
|
+
|
|
282
|
+
**Markdown-driven image input + extensibility (option C).** Not built yet — current
|
|
283
|
+
inline-image support (toolbar / paste / drop) is considered sufficient for now. When
|
|
284
|
+
this is picked up, do all three parts together:
|
|
285
|
+
|
|
286
|
+
1. **Expose an extension seam** — surface `parcel.extensions: Array<AnyExtension>`
|
|
287
|
+
through to `buildEditorExtensions` (the internal `extraExtensions` param already
|
|
288
|
+
exists; it just isn't wired to the parcel/SFC yet). This lets consumers plug in any
|
|
289
|
+
future input style (markdown shortcuts, slash commands, emoji, …) without forking
|
|
290
|
+
the core. This is the primary lever for handling diverse input modes.
|
|
291
|
+
2. **Decouple Image-node registration from the upload adapter** — e.g. allow
|
|
292
|
+
`parcel.image: true | { uploadImage }`. `true` registers the Image node so
|
|
293
|
+
URL-based images (markdown ``, pasted URLs) render *without* requiring an
|
|
294
|
+
upload adapter; the object form additionally enables file upload. Today the Image
|
|
295
|
+
node is registered only when `parcel.image.uploadImage` is present, which couples
|
|
296
|
+
plain URL images to the upload path.
|
|
297
|
+
3. **Ship a markdown-image input rule** — an opt-in Tiptap input rule so typing
|
|
298
|
+
`` auto-converts to an image as you type (paste of `` already
|
|
299
|
+
works today via the markdown-paste layer + `marked`). Domain-agnostic; plugs in via
|
|
300
|
+
the seam from (1).
|
|
301
|
+
|
|
302
|
+
Note: markdown image syntax refers to an existing URL, so it intentionally bypasses
|
|
303
|
+
`uploadImage` (there is no file to upload) — which is why part (2) is needed for the
|
|
304
|
+
markdown path to work independently of the upload adapter.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# `<FuroEmailField>` Interface Specification
|
|
2
|
+
|
|
3
|
+
`<FuroEmailField>` follows the same architecture as [`<FuroTextField>`](./furo-text-field.md). Read that document first — this page only lists the differences.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Differences from `<FuroTextField>`
|
|
8
|
+
|
|
9
|
+
### `type` attribute
|
|
10
|
+
|
|
11
|
+
Always renders `type="email"`. The `type` fallthrough attribute is excluded — the underlying control is always an HTML email input (`<input type="email">`), which enables the browser’s built‑in email format hints and constraint validation API where supported.
|
|
12
|
+
|
|
13
|
+
### CSS class
|
|
14
|
+
|
|
15
|
+
`.unit-form-control.email` instead of `.unit-form-control.text`.
|
|
16
|
+
|
|
17
|
+
### `parcel.value` type
|
|
18
|
+
|
|
19
|
+
| Component | `parcel.value` type |
|
|
20
|
+
|---------------------|----------------------------|
|
|
21
|
+
| `<FuroTextField>` | `string \| number \| null` |
|
|
22
|
+
| `<FuroEmailField>` | `string \| null` |
|
|
23
|
+
|
|
24
|
+
Email fields only accept textual values; numeric `parcel.value` is not supported.
|
|
25
|
+
|
|
26
|
+
### Class naming
|
|
27
|
+
|
|
28
|
+
All class and type names are prefixed with `Email` instead of `Text`:
|
|
29
|
+
|
|
30
|
+
| Role | `FuroTextField` | `FuroEmailField` |
|
|
31
|
+
|----------------|---------------------------|--------------------------------|
|
|
32
|
+
| Component | `FuroTextField` | `FuroEmailField` |
|
|
33
|
+
| Context | `FuroTextFieldContext` | `FuroEmailFieldContext` |
|
|
34
|
+
| EmitPayload | `TextFieldEmitPayload` | `EmailFieldEmitPayload` |
|
|
35
|
+
| Parcel typedef | `FuroTextFieldParcel` | `FuroEmailFieldParcel` |
|
|
36
|
+
|
|
37
|
+
### `EmailFieldEmitPayload`
|
|
38
|
+
|
|
39
|
+
`EmailFieldEmitPayload` extends [`BaseEmitPayload`](../lib/emit-payload/BaseEmitPayload.js) with no additional members beyond the shared text-field helpers. The API (`.value`, `.extractTrimmedValue()`, `.hasEmptyText()`, `.hasEnteredTrimmedText()`, etc.) matches [`TextFieldEmitPayload`](./furo-text-field.md#textfieldemitpayload-api), which also extends `BaseEmitPayload`.
|
|
40
|
+
|
|
41
|
+
Structured email validation (format, domain rules, uniqueness) stays in the parent Context; `<FuroEmailField>` does not enforce a format beyond what the native `email` input type provides.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Fallthrough attributes (recommended usage)
|
|
46
|
+
|
|
47
|
+
Same fallthrough rules as `<FuroTextField>`. Typical pairings:
|
|
48
|
+
|
|
49
|
+
| Attribute | Typical value | Notes |
|
|
50
|
+
|----------------|-----------------|-------|
|
|
51
|
+
| `autocomplete` | `email` | Single email field; use `username` only when it doubles as login identifier per HTML autofill guidelines. |
|
|
52
|
+
| `name` | e.g. `email` | Stable form field name for submission. |
|
|
53
|
+
| `placeholder` | Free text | UX hint only — not a substitute for an accessible label. |
|
|
54
|
+
|
|
55
|
+
Optional native attributes remain available when passed as fallthrough (for example `required`, `disabled`, `readonly`, `maxlength`, `pattern`) per project needs.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Example
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<FuroEmailField
|
|
63
|
+
:parcel="{
|
|
64
|
+
value: context.emailRef.value,
|
|
65
|
+
invalid: context.statusReactive.isEmailInvalid,
|
|
66
|
+
}"
|
|
67
|
+
:required="true"
|
|
68
|
+
placeholder="you@example.com"
|
|
69
|
+
name="email"
|
|
70
|
+
autocomplete="email"
|
|
71
|
+
@change-value="context.onChangeValue({
|
|
72
|
+
payload: $event
|
|
73
|
+
})"
|
|
74
|
+
/>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
onChangeValue ({
|
|
79
|
+
payload
|
|
80
|
+
}) {
|
|
81
|
+
if (payload.hasEmptyTrimmedText()) {
|
|
82
|
+
this.statusReactive.isEmailInvalid = true
|
|
83
|
+
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.statusReactive.isEmailInvalid = !this.validatesEmail({
|
|
88
|
+
payload
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# FuroEmptyState
|
|
2
|
+
|
|
3
|
+
Presentational placeholder shown when a region has no content to display — no views, no records, or nothing found. Title and description text come from the parcel so the consumer owns the wording; an icon and action controls are supplied via slots. It carries no value and emits nothing.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Layer** | Molecule |
|
|
10
|
+
| **Path** | `lib/components/molecules/FuroEmptyState/` |
|
|
11
|
+
| **Base primitive** | None — a styled centered block (no headless primitive needed) |
|
|
12
|
+
| **Composes** | Nothing (action slot typically hosts `FuroButton`) |
|
|
13
|
+
| **Emits** | None |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Public API
|
|
18
|
+
|
|
19
|
+
### Props
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `parcel` | `EmptyStateParcel \| null` | `null` | Reactive data object holding the title and description text. |
|
|
24
|
+
|
|
25
|
+
### Parcel keys
|
|
26
|
+
|
|
27
|
+
| Key | Type | Default | Description |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `title` | `string` | — | Heading text. Rendered only when supplied; the consumer owns the wording. |
|
|
30
|
+
| `description` | `string` | — | Supporting text under the title. Rendered only when supplied. |
|
|
31
|
+
|
|
32
|
+
### Emits
|
|
33
|
+
|
|
34
|
+
None. The empty state is presentational; wire actions through the `action` slot.
|
|
35
|
+
|
|
36
|
+
### Slots
|
|
37
|
+
|
|
38
|
+
| Slot | Description |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `icon` | Leading icon or illustration above the title. |
|
|
41
|
+
| `action` | Action controls (e.g. a "Create" `FuroButton`) below the text. |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Behavior
|
|
46
|
+
|
|
47
|
+
| Concern | Behavior |
|
|
48
|
+
|---|---|
|
|
49
|
+
| **Title / description** | `title` and `description` getters extract from `parcel` (null when absent). Each block renders only when its text is present, so there are no hardcoded strings. |
|
|
50
|
+
| **Icon / action** | Rendered only when the matching slot is provided. |
|
|
51
|
+
| **Semantics** | Plain visible content; the consumer announces the empty state on the surrounding region when needed. |
|
|
52
|
+
| **Passthrough** | Extra attributes fall through to the root (`inheritAttrs: false` + `v-bind="$attrs"`). |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```vue
|
|
59
|
+
<!-- No records, with icon and action -->
|
|
60
|
+
<FuroEmptyState
|
|
61
|
+
:parcel="{
|
|
62
|
+
title: 'No records yet',
|
|
63
|
+
description: 'Create your first record to see it listed here.',
|
|
64
|
+
}"
|
|
65
|
+
>
|
|
66
|
+
<template #icon>
|
|
67
|
+
<Icon name="ph:tray" />
|
|
68
|
+
</template>
|
|
69
|
+
<template #action>
|
|
70
|
+
<FuroButton>Create record</FuroButton>
|
|
71
|
+
</template>
|
|
72
|
+
</FuroEmptyState>
|
|
73
|
+
|
|
74
|
+
<!-- Title only -->
|
|
75
|
+
<FuroEmptyState :parcel="{ title: 'Nothing to show' }" />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Composition notes
|
|
81
|
+
|
|
82
|
+
- Imports nothing from the app — no GraphQL, PrimeVue, Vue Router, or Nuxt-specific imports.
|
|
83
|
+
- All logic lives in `FuroEmptyStateContext` as extract-only getters (`title`, `description`).
|
|
84
|
+
- CSS is wrapped in `@layer furo`; spacing uses `--size-space-*` for `gap`, text colors reference semantic `--color-foreground` / `--color-muted-foreground` tokens only.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Testing
|
|
89
|
+
|
|
90
|
+
- Context logic (`title` / `description` getters) is fully covered by jsdom unit tests, including the empty-parcel and null-parcel fallbacks.
|
|
91
|
+
- No SFC mount test: behavior is declarative bindings over the tested context. Rendered output (icon / title / description / action) is verified by manual QA in the playground.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Playground
|
|
96
|
+
|
|
97
|
+
`/samples/components/playground` → **Empty State** tab. Demonstrates a no-records state with icon + action, a no-results state with icon only, and a title-only state.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# FuroErrorState
|
|
2
|
+
|
|
3
|
+
Presentational placeholder shown when a region fails to load — a fetch error or a failed request. Title and description text come from the parcel so the consumer owns the wording; an icon and action controls (typically a retry button) are supplied via slots. It carries no value and emits nothing.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Layer** | Molecule |
|
|
10
|
+
| **Path** | `lib/components/molecules/FuroErrorState/` |
|
|
11
|
+
| **Base primitive** | None — a styled centered block (no headless primitive needed) |
|
|
12
|
+
| **Composes** | Nothing (action slot typically hosts `FuroButton`) |
|
|
13
|
+
| **Emits** | None |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Public API
|
|
18
|
+
|
|
19
|
+
### Props
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| `parcel` | `ErrorStateParcel \| null` | `null` | Reactive data object holding the title and description text. |
|
|
24
|
+
|
|
25
|
+
### Parcel keys
|
|
26
|
+
|
|
27
|
+
| Key | Type | Default | Description |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `title` | `string` | — | Heading text. Rendered only when supplied; the consumer owns the wording. |
|
|
30
|
+
| `description` | `string` | — | Supporting text under the title. Rendered only when supplied. |
|
|
31
|
+
|
|
32
|
+
### Emits
|
|
33
|
+
|
|
34
|
+
None. The error state is presentational; wire the retry through the `action` slot.
|
|
35
|
+
|
|
36
|
+
### Slots
|
|
37
|
+
|
|
38
|
+
| Slot | Description |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `icon` | Leading icon or illustration above the title. |
|
|
41
|
+
| `action` | Action controls (e.g. a "Retry" `FuroButton`) below the text. |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Behavior
|
|
46
|
+
|
|
47
|
+
| Concern | Behavior |
|
|
48
|
+
|---|---|
|
|
49
|
+
| **Title / description** | `title` and `description` getters extract from `parcel` (null when absent). Each block renders only when its text is present, so there are no hardcoded strings. |
|
|
50
|
+
| **Icon / action** | Rendered only when the matching slot is provided. |
|
|
51
|
+
| **Semantics** | Root carries `role="alert"` so assistive technology announces the failure; the icon uses the destructive color token. |
|
|
52
|
+
| **Passthrough** | Extra attributes fall through to the root (`inheritAttrs: false` + `v-bind="$attrs"`). |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```vue
|
|
59
|
+
<!-- Fetch failed, with icon and retry action -->
|
|
60
|
+
<FuroErrorState
|
|
61
|
+
:parcel="{
|
|
62
|
+
title: 'Failed to load records',
|
|
63
|
+
description: 'Check your connection and try again.',
|
|
64
|
+
}"
|
|
65
|
+
>
|
|
66
|
+
<template #icon>
|
|
67
|
+
<Icon name="ph:warning-circle" />
|
|
68
|
+
</template>
|
|
69
|
+
<template #action>
|
|
70
|
+
<FuroButton @click="refetch">Retry</FuroButton>
|
|
71
|
+
</template>
|
|
72
|
+
</FuroErrorState>
|
|
73
|
+
|
|
74
|
+
<!-- Title only -->
|
|
75
|
+
<FuroErrorState :parcel="{ title: 'Unable to load' }" />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Composition notes
|
|
81
|
+
|
|
82
|
+
- Imports nothing from the app — no GraphQL, PrimeVue, Vue Router, or Nuxt-specific imports.
|
|
83
|
+
- All logic lives in `FuroErrorStateContext` as extract-only getters (`title`, `description`).
|
|
84
|
+
- CSS is wrapped in `@layer furo`; spacing uses `--size-space-*` for `gap`, the icon references the semantic `--color-destructive-text` token and text colors reference `--color-foreground` / `--color-muted-foreground` only.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Testing
|
|
89
|
+
|
|
90
|
+
- Context logic (`title` / `description` getters) is fully covered by jsdom unit tests, including the empty-parcel and null-parcel fallbacks.
|
|
91
|
+
- No SFC mount test: behavior is declarative bindings over the tested context. Rendered output (icon / title / description / action) is verified by manual QA in the playground.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Playground
|
|
96
|
+
|
|
97
|
+
`/samples/components/playground` → **Error State** tab. Demonstrates a fetch-failed state with icon + retry action, a server-error state with icon only, and a title-only state.
|