actual-css 0.9.2 → 0.11.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/CHANGELOG.md +169 -1
- package/README.md +65 -44
- package/components.json +35 -14
- package/dist/actual.full.css +877 -583
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/app-ui.md +1 -1
- package/docs/design-notes/column-layout.md +103 -520
- package/docs/design-notes/enhancement-contract.md +16 -7
- package/docs/design-notes/forced-colors.md +12 -1
- package/docs/design-notes/naming.md +1 -1
- package/docs/design-notes/platform-alignment.md +8 -4
- package/docs/design-notes/rich-text.md +61 -0
- package/docs/design-notes/steps.md +2 -2
- package/docs/design-notes/surface-reparenting.md +8 -9
- package/docs/navigation.json +4 -0
- package/docs/pages/components/accordion.md +55 -1
- package/docs/pages/components/alert.md +9 -15
- package/docs/pages/components/aura.md +12 -13
- package/docs/pages/components/badge.md +16 -7
- package/docs/pages/components/bleed.md +42 -0
- package/docs/pages/components/button.md +30 -4
- package/docs/pages/components/card.md +21 -86
- package/docs/pages/components/chat.md +5 -0
- package/docs/pages/components/close.md +48 -0
- package/docs/pages/components/data-list.md +26 -1
- package/docs/pages/components/dialog.md +21 -18
- package/docs/pages/components/drawer.md +9 -9
- package/docs/pages/components/navbar.md +5 -5
- package/docs/pages/components/pagination.md +15 -15
- package/docs/pages/components/shimmer.md +57 -0
- package/docs/pages/components/skeleton.md +2 -1
- package/docs/pages/components/spinner.md +14 -9
- package/docs/pages/components/table.md +13 -0
- package/docs/pages/enhancements/flyout.md +2 -0
- package/docs/pages/enhancements/overview.md +5 -0
- package/docs/pages/enhancements/tabs.md +4 -0
- package/docs/pages/enhancements/tooltip.md +3 -1
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/choice-cards.md +116 -4
- package/docs/pages/forms/combobox.md +39 -12
- package/docs/pages/forms/joined-controls.md +2 -5
- package/docs/pages/forms/overview.md +10 -1
- package/docs/pages/forms/range.md +110 -5
- package/docs/pages/forms/rich-text.md +110 -0
- package/docs/pages/forms/validation.md +5 -1
- package/docs/pages/foundations/theming.md +110 -18
- package/docs/pages/foundations/tokens.md +41 -13
- package/docs/pages/guides/bootstrap.md +2 -1
- package/docs/pages/guides/introduction.md +15 -23
- package/docs/pages/guides/modular-import.md +54 -7
- package/docs/pages/guides/progressive-enhancement.md +19 -17
- package/docs/pages/guides/tailwind.md +21 -7
- package/docs/pages/layout/column-layout.md +92 -54
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/utilities/overview.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +5 -3
- package/llms.txt +25 -4
- package/package.json +10 -6
- package/reserved-classes.json +6 -30
- package/skills/actual-css/PROJECT.md.example +65 -0
- package/skills/actual-css/README.md +64 -0
- package/skills/actual-css/SKILL.md +234 -0
- package/skills/actual-css/references/accessibility.md +44 -0
- package/skills/actual-css/references/api-and-vocabulary.md +94 -0
- package/skills/actual-css/references/enhancements.md +59 -0
- package/skills/actual-css/references/forms.md +49 -0
- package/skills/actual-css/references/layout-and-theming.md +82 -0
- package/skills/actual-css/references/review-checklist.md +51 -0
- package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
- package/src/css/components/accordion.css +44 -7
- package/src/css/components/alert.css +23 -43
- package/src/css/components/app-nav.css +9 -0
- package/src/css/components/avatar.css +1 -1
- package/src/css/components/badge.css +11 -39
- package/src/css/components/bleed.css +79 -0
- package/src/css/components/button.css +6 -17
- package/src/css/components/card.css +7 -40
- package/src/css/components/close.css +91 -0
- package/src/css/components/data-list.css +18 -0
- package/src/css/components/drawer.css +15 -56
- package/src/css/components/fab.css +10 -0
- package/src/css/components/flyout.css +5 -1
- package/src/css/components/index.css +2 -0
- package/src/css/components/join.css +36 -20
- package/src/css/components/menu.css +12 -0
- package/src/css/components/meter.css +1 -1
- package/src/css/components/modal.css +10 -57
- package/src/css/components/navbar.css +27 -0
- package/src/css/components/pagination.css +3 -2
- package/src/css/components/rating.css +1 -1
- package/src/css/components/status-bar.css +2 -2
- package/src/css/components/steps.css +3 -3
- package/src/css/components/tab.css +3 -3
- package/src/css/components/table.css +6 -0
- package/src/css/components/tooltip.css +1 -1
- package/src/css/core/focus.css +19 -3
- package/src/css/core/theme.css +33 -43
- package/src/css/core/tokens.css +9 -5
- package/src/css/core/variants.css +5 -34
- package/src/css/effects/aura.css +51 -33
- package/src/css/effects/index.css +1 -0
- package/src/css/effects/shimmer.css +57 -0
- package/src/css/forms/choice-card.css +142 -24
- package/src/css/forms/choice.css +2 -3
- package/src/css/forms/control.css +17 -10
- package/src/css/forms/custom-select.css +4 -4
- package/src/css/forms/native.css +4 -4
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +72 -14
- package/src/css/forms/switch.css +2 -3
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/column-layout.css +26 -111
- package/src/css/utilities/extra.css +7 -6
- package/src/js/command.js +61 -32
- package/src/js/dialog.js +103 -47
- package/src/js/dismiss.js +1 -2
- package/src/js/enhance.js +91 -126
- package/src/js/escape.js +1 -0
- package/src/js/flyout.js +10 -1
- package/src/js/focus-group.js +2 -1
- package/src/js/full.js +1 -0
- package/src/js/keys.js +14 -0
- package/src/js/menu.js +2 -1
- package/src/js/password.js +1 -8
- package/src/js/range.js +151 -0
- package/src/js/scrollspy.js +4 -5
- package/src/js/status.js +21 -10
- package/src/js/surface.js +12 -8
- package/src/js/tab.js +2 -1
- package/src/js/tooltip.js +29 -2
- package/src/js/validation.js +8 -15
- package/dist/actual.css +0 -876
- package/dist/actual.js +0 -1
- package/dist/actual.min.css +0 -1
|
@@ -8,6 +8,8 @@ and arrow position are written by JavaScript; the element is shown and hidden
|
|
|
8
8
|
by promoting it to the top layer with `popover="manual"`.
|
|
9
9
|
|
|
10
10
|
- Use `data-tooltip` on the trigger. With text (`data-tooltip="Help"`), the tooltip element is generated. Empty (`data-tooltip`) marks an explicit tooltip connected via `aria-describedby`.
|
|
11
|
+
- Generated text is a snapshot at show time: changing `data-tooltip` never rewrites a tooltip that is already shown, and an always-visible generated tooltip keeps its initial text. For content that must update dynamically, use an explicit tooltip. See the [tooltips bench](../examples/overview.md) (`demo/templates/tooltips.html`) for all six cases side by side.
|
|
12
|
+
- Generated text is always plain text (`textContent` only), so a shorthand value is safe for untrusted strings once they are in the DOM. An explicit tooltip is author-owned DOM: its sanitization belongs to whoever produces it. Escape the attribute server-side — a DOM already compromised at render cannot be repaired at read time. There is no `data-tooltip-html`, by design.
|
|
11
13
|
- Tooltips are supplemental. Do not put required information or interactive controls inside them.
|
|
12
14
|
- Show on hover and focus. Hide on Escape, blur, or pointer leave.
|
|
13
15
|
- A trigger scrolled out of view takes its tooltip down and brings it back when it returns; only a dismissal ends the tooltip.
|
|
@@ -35,7 +37,7 @@ runtime when Actual should own showing and hiding.
|
|
|
35
37
|
|
|
36
38
|
**Where the element lives is part of the contract.** An explicit tooltip stays
|
|
37
39
|
exactly where you wrote it, so it keeps every scope that reaches it by
|
|
38
|
-
inheritance — a theme island, a density scope,
|
|
40
|
+
inheritance — a theme island, a density scope, your own scoped
|
|
39
41
|
custom properties. It also means placement is yours to get right: a tooltip
|
|
40
42
|
used from inside a modal dialog must be authored inside that dialog, because
|
|
41
43
|
the top layer does not lift an element out of a modal's inertness.
|
|
@@ -47,12 +47,13 @@ One mechanism at a time, with its contract and its pitfalls.
|
|
|
47
47
|
Contracts you cannot read off a static screenshot: state, motion, focus, and
|
|
48
48
|
extension points.
|
|
49
49
|
|
|
50
|
-
- [
|
|
50
|
+
- [Contrast contexts](../../demo/templates/contrast-contexts.html) - the page, `data-theme` islands, a card island, a named theme and an application-painted band side by side, with every intent variant, a full form (combobox and date picker included) and focus line measured live, plus joined fields that need no focus adaptation
|
|
51
51
|
- [Presence motion](../../demo/templates/motion.html) - status bar, tooltip, flyout, modal and drawer replayed side by side, with easing swaps and a reduced-motion simulation
|
|
52
52
|
- [Keyboard controls](../../demo/templates/keyboard-controls.html) - hands-on keyboard support matrix and focus test bench
|
|
53
53
|
- [Adaptive filter surface](../../demo/templates/adaptive-surface.html) - app-level flyout/drawer composition with one synchronized filter state
|
|
54
54
|
- [Select intents](../../demo/templates/select-intents.html) - extending the enhancer with a project's own `data-enhance` behavior, injected markup included, with no framework change
|
|
55
55
|
- [Popover transport probe](../../demo/templates/popover-transport.html) - self-scoring bench for the native popover transport go/no-go
|
|
56
|
+
- [Tooltips](../../demo/templates/tooltips.html) - generated text is a snapshot at show time: what updates, what stays, and the explicit-tip way for dynamic content
|
|
56
57
|
|
|
57
58
|
### Third-party components on Actual tokens
|
|
58
59
|
|
|
@@ -7,12 +7,35 @@
|
|
|
7
7
|
| Class | Kind | Description |
|
|
8
8
|
| ------------------------- | --------- | --------------------------------------------- |
|
|
9
9
|
| `.choice-card` | Component | Card-style `<label>` around a native control. |
|
|
10
|
+
| `.chip` | Modifier | Compact pill form: filter chips, tags. |
|
|
10
11
|
| `.primary` / `.secondary` | Intent | Accent for the selected state. |
|
|
11
12
|
|
|
12
13
|
- Use `.choice-card` on a `<label>` wrapping a native `<input type="radio">` or `<input type="checkbox">`. The input is visually hidden; in browsers without `:has()` it stays visible so selection state never disappears.
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
14
|
+
- The control shows before any choice: a ring (radio) or a rounded box (checkbox) on the leading edge, level with the first line. Checked, it fills with a check, and the card takes a primary border and a subtle tint.
|
|
15
|
+
- Each child element of the label sits on its own row beside the indicator — a title and a `.muted` detail need no wrapper. Wrap loose text in an element.
|
|
16
|
+
- Stack cards for a list of options (a person, an address, a delivery method), or pair them with grid utilities for options compared side by side.
|
|
17
|
+
- Supports disabled, checked, focus-visible, and invalid states; the resting ring turns danger with the border.
|
|
18
|
+
|
|
19
|
+
```html demo
|
|
20
|
+
<fieldset class="field-group stack">
|
|
21
|
+
<legend class="field-label">Deliver to</legend>
|
|
22
|
+
<label class="choice-card">
|
|
23
|
+
<input type="radio" name="address" value="home" checked />
|
|
24
|
+
<strong>Home</strong>
|
|
25
|
+
<span class="muted">12 Rue des Tanneurs, 1000 Brussels</span>
|
|
26
|
+
</label>
|
|
27
|
+
<label class="choice-card">
|
|
28
|
+
<input type="radio" name="address" value="work" />
|
|
29
|
+
<strong>Work</strong>
|
|
30
|
+
<span class="muted">Avenue Louise 54, 1050 Ixelles</span>
|
|
31
|
+
</label>
|
|
32
|
+
<label class="choice-card">
|
|
33
|
+
<input type="radio" name="address" value="pickup" />
|
|
34
|
+
<strong>Pickup point</strong>
|
|
35
|
+
<span class="muted">Collect within 7 days</span>
|
|
36
|
+
</label>
|
|
37
|
+
</fieldset>
|
|
38
|
+
```
|
|
16
39
|
|
|
17
40
|
```html demo
|
|
18
41
|
<div class="grid-3">
|
|
@@ -93,11 +116,100 @@ follows through its `em` sizing. Padding is density, so use a `.compact` or
|
|
|
93
116
|
`.spacious` context to change it. Intent classes (`.primary`, `.secondary`) are
|
|
94
117
|
supported on the `.choice-card` element.
|
|
95
118
|
|
|
119
|
+
## Corner badge
|
|
120
|
+
|
|
121
|
+
A tile whose content is the choice itself — a product image, a centred plan
|
|
122
|
+
name — may want no indicator column, and a check badge over its corner once
|
|
123
|
+
selected (radio/checkbox tile, selected tick, check in the corner). That is a
|
|
124
|
+
recipe on the card's own `::before` (disc) and `::after` (check) layers,
|
|
125
|
+
written in application CSS after the framework:
|
|
126
|
+
|
|
127
|
+
```css
|
|
128
|
+
.choice-tile {
|
|
129
|
+
--choice-card-check-size: 1.5em;
|
|
130
|
+
|
|
131
|
+
display: block;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.choice-tile::before,
|
|
135
|
+
.choice-tile::after {
|
|
136
|
+
position: absolute;
|
|
137
|
+
inset-inline-end: -0.375em;
|
|
138
|
+
inset-block-start: -0.375em;
|
|
139
|
+
margin: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.choice-tile::before {
|
|
143
|
+
border: 0;
|
|
144
|
+
box-shadow: 0 0 0 2px var(--surface-opaque);
|
|
145
|
+
transform: scale(0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.choice-tile:has(:checked)::before {
|
|
149
|
+
transform: scale(1);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<label class="choice-card choice-tile">
|
|
155
|
+
<input type="radio" name="cpu" value="1" checked />
|
|
156
|
+
<strong>8-core CPU</strong><br />
|
|
157
|
+
<span class="muted">32 GB RAM</span>
|
|
158
|
+
</label>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The badge overhangs the corner, so no ancestor up to the grid may clip with
|
|
162
|
+
`overflow: hidden`. The [Neon Ramen item page](../../demo/sites/neon-ramen/item.html)
|
|
163
|
+
uses it for its sizes and toppings.
|
|
164
|
+
|
|
165
|
+
## Chips
|
|
166
|
+
|
|
167
|
+
Add `.chip` for the compact pill form: filter chips, selectable tags, toggle
|
|
168
|
+
pills, multi-select facets. A radio group gives single-choice chips. A leading
|
|
169
|
+
dot turns into the check in a fixed slot, so checking never moves the label.
|
|
170
|
+
Use `.cluster` for a wrapping row. A chip is sized like a control: `.btn` and
|
|
171
|
+
`.input` height, `--control-pad-x` inline padding, `.sm` / `.lg` scale.
|
|
172
|
+
A chip is a `<label>` around its checkbox/radio — never nest a `.close` or any
|
|
173
|
+
other button inside it. A removable value (combobox tag) belongs to the widget
|
|
174
|
+
that produced it, or to a sibling `.badge` + `.close`, not to the chip label.
|
|
175
|
+
|
|
176
|
+
```html demo
|
|
177
|
+
<fieldset class="field-group">
|
|
178
|
+
<legend class="field-label">Fruit</legend>
|
|
179
|
+
<div class="cluster">
|
|
180
|
+
<label class="choice-card chip">
|
|
181
|
+
<input type="checkbox" name="fruit" value="pear" checked />
|
|
182
|
+
Pear
|
|
183
|
+
</label>
|
|
184
|
+
<label class="choice-card chip">
|
|
185
|
+
<input type="checkbox" name="fruit" value="apple" />
|
|
186
|
+
Apple
|
|
187
|
+
</label>
|
|
188
|
+
<label class="choice-card chip">
|
|
189
|
+
<input type="checkbox" name="fruit" value="plum" disabled />
|
|
190
|
+
Plum
|
|
191
|
+
</label>
|
|
192
|
+
</div>
|
|
193
|
+
</fieldset>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The dot is `::before`: a full-chip layer clipped to a `circle()` centred on
|
|
197
|
+
the check slot (`--choice-chip-dot`). A theme can grow it into a selected fill
|
|
198
|
+
by widening the clip, as the `material` theme does.
|
|
199
|
+
|
|
96
200
|
## CSS hooks
|
|
97
201
|
|
|
98
202
|
- `--choice-card-pad` — inner padding.
|
|
203
|
+
- `--choice-card-gap` — space between the indicator column and the label.
|
|
99
204
|
- `--choice-card-radius` — corner radius.
|
|
100
|
-
- `--choice-card-check-size` — size of the
|
|
205
|
+
- `--choice-card-check-size` — size of the indicator, and the width of its
|
|
206
|
+
column.
|
|
207
|
+
- `--choice-card-border` / `--choice-card-bg` — resting border and background;
|
|
208
|
+
the checked state and hover rebind them, so a checked override needs
|
|
209
|
+
`:has(:checked)`.
|
|
210
|
+
- `--choice-chip-dot` — read-only: the chip's check-slot centre as a `circle()`
|
|
211
|
+
position. It is declared on the chip's `::before`, so read it in a
|
|
212
|
+
`.choice-card.chip…::before` rule.
|
|
101
213
|
|
|
102
214
|
Plain `.check` / `.radio` controls expose two hooks:
|
|
103
215
|
|
|
@@ -10,7 +10,7 @@ real product needs a JavaScript combobox (searchable select, removable tags,
|
|
|
10
10
|
autocomplete suggestions), load a dedicated component and bridge its custom
|
|
11
11
|
properties onto Actual's tokens.
|
|
12
12
|
|
|
13
|
-
**Related terms:** combo box, combobox, autocomplete, datalist, searchable select, enhanced select, multiple select, tags, chips, tag input, pill input.
|
|
13
|
+
**Related terms:** combo box, combobox, autocomplete, datalist, select, custom select, native select, searchable select, enhanced select, multiple select, dropdown, form controls, tags, chips, tag input, pill input.
|
|
14
14
|
|
|
15
15
|
## Theme bridge
|
|
16
16
|
|
|
@@ -36,6 +36,21 @@ mix tokens.
|
|
|
36
36
|
|
|
37
37
|
The card in the template documents the full token map of this recipe.
|
|
38
38
|
|
|
39
|
+
## Bridge contract
|
|
40
|
+
|
|
41
|
+
Actual owns the visual language; the widget owns the behavior. Actual provides
|
|
42
|
+
surface, border, radius, inset focus outline, danger state, `.join` corners,
|
|
43
|
+
and the contrast contracts those tokens carry. The widget provides search,
|
|
44
|
+
keyboard navigation, multi-value state, removable tags, and option rendering.
|
|
45
|
+
The bridge CSS stays in docs/demo — it is a recipe, not package API, so no
|
|
46
|
+
`actual-combobox.css` ships with the framework and no widget version is pinned
|
|
47
|
+
by it.
|
|
48
|
+
|
|
49
|
+
Multi-value and i18n (for example a "n selected" summary string) are widget
|
|
50
|
+
configuration: pass locale strings and tag rendering through the widget's own
|
|
51
|
+
options, and keep skinning on the `--cb-*` tokens so light/dark mode and preset
|
|
52
|
+
themes keep applying for free.
|
|
53
|
+
|
|
39
54
|
## Lightweight pass-through
|
|
40
55
|
|
|
41
56
|
Most of the skin is three one-line token bridges. The visible vocabulary —
|
|
@@ -47,7 +62,7 @@ contrast contracts apply to the combobox for free:
|
|
|
47
62
|
.actual-combobox {
|
|
48
63
|
--cb-bg: var(--surface);
|
|
49
64
|
--cb-color: var(--text);
|
|
50
|
-
--cb-border-color: var(--border);
|
|
65
|
+
--cb-border-color: var(--form-invalid-border, var(--border));
|
|
51
66
|
--cb-border-radius: var(--radius);
|
|
52
67
|
--cb-focus-color: var(--focus);
|
|
53
68
|
--cb-muted-color: var(--text-muted);
|
|
@@ -57,15 +72,23 @@ contrast contracts apply to the combobox for free:
|
|
|
57
72
|
--cb-option-min-height: var(--control-size);
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
.actual-combobox .cb-control:focus-
|
|
61
|
-
|
|
62
|
-
outline:
|
|
63
|
-
|
|
75
|
+
.actual-combobox :is(.cb-text-control:focus-visible, .cb-control:has(.cb-input:focus-visible)) {
|
|
76
|
+
border-color: var(--form-invalid-border, var(--cb-border-color));
|
|
77
|
+
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
|
|
78
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
79
|
+
box-shadow: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.actual-combobox :is(.cb-text-control[aria-invalid="true"], .cb-control:has([aria-invalid="true"])) {
|
|
83
|
+
--form-invalid-border: var(--danger);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:where(.actual-combobox) combo-box {
|
|
87
|
+
border-radius: var(--cb-border-radius);
|
|
64
88
|
}
|
|
65
89
|
|
|
66
|
-
.actual-combobox .cb-
|
|
67
|
-
|
|
68
|
-
box-shadow: 0 0 0 2px var(--focus-ring);
|
|
90
|
+
.actual-combobox :is(.cb-control, .cb-text-control) {
|
|
91
|
+
border-radius: inherit;
|
|
69
92
|
}
|
|
70
93
|
|
|
71
94
|
.actual-combobox .cb-chip {
|
|
@@ -79,9 +102,13 @@ contrast contracts apply to the combobox for free:
|
|
|
79
102
|
}
|
|
80
103
|
```
|
|
81
104
|
|
|
82
|
-
|
|
83
|
-
input
|
|
84
|
-
|
|
105
|
+
While the search input is focused, the control takes the same inset outline as
|
|
106
|
+
`.input` in place of the library's accent border and outer halo; a focused chip
|
|
107
|
+
or remove × keeps the library's own inner ring. Being inset, the outline needs
|
|
108
|
+
no adaptation inside `.join`: `combo-box` owns the radius at zero specificity,
|
|
109
|
+
so the join gives it the group corners and the visible control inherits them. The invalid rule sets `--form-invalid-border`,
|
|
110
|
+
the hook `.field.danger` and `aria-invalid` already use, so border and outline
|
|
111
|
+
turn danger together.
|
|
85
112
|
|
|
86
113
|
The same approach works for any widget that exposes a custom-property skin —
|
|
87
114
|
keep the widget's behavior, restyle its tokens through the shared vocabulary.
|
|
@@ -29,11 +29,8 @@ When composing source files manually, import `components/join.css` after the con
|
|
|
29
29
|
</div>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
`.join` handles border-radius and border collapsing between adjacent children. It works with any direct child — `.input`, `.btn`, `.select`, or `.join-addon`.
|
|
33
|
-
When an `.input`, `.textarea`, or `.select` receives keyboard
|
|
34
|
-
the focus ring surrounds the complete joined field. Attached buttons keep their
|
|
35
|
-
own focus indicator when reached directly, so the actionable segment remains
|
|
36
|
-
identifiable.
|
|
32
|
+
`.join` handles border-radius and border collapsing between adjacent children. It works with any direct child — `.input`, `.btn`, `.select`, or `.join-addon` — plus one wrapper level (e.g. a combobox element around the native control): the wrapper receives the group corners and the inner control inherits them via `border-radius: inherit`.
|
|
33
|
+
When an `.input`, `.textarea`, or `.select` receives keyboard focus, it keeps its own inset focus outline; the group never moves the ring onto itself, so the focused segment remains identifiable.
|
|
37
34
|
|
|
38
35
|
## Linting `role="group"`
|
|
39
36
|
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
- `.field-group` and `.choice` provide layout without depending on parent scope.
|
|
11
11
|
- Customizable select is progressive enhancement only — the native select remains the baseline.
|
|
12
12
|
- Searchable selects, tags and autocomplete are a JavaScript widget concern; theme an external combobox with Actual tokens — see the [combobox template](../../demo/templates/combobox.html).
|
|
13
|
+
- Formatted text (bold, lists, links) is a hosted-editor concern; the native textarea stays the baseline and an external engine is themed the same way — see the [rich-text template](../../demo/templates/rich-text.html) and the [composer template](../../demo/templates/composer.html) for the plain-text case.
|
|
14
|
+
|
|
15
|
+
**Related terms:** form controls, select, custom select, dropdown, input, textarea, checkbox, radio.
|
|
13
16
|
|
|
14
17
|
## Class reference
|
|
15
18
|
|
|
@@ -165,6 +168,12 @@ A full form using the control classes, field wrappers, and form actions:
|
|
|
165
168
|
</form>
|
|
166
169
|
```
|
|
167
170
|
|
|
171
|
+
## Hooks
|
|
172
|
+
|
|
173
|
+
Set `--control-hover-border` to tune the hover border of `.input` and `.textarea`.
|
|
174
|
+
Set `--control-hover-bg` to keep a `.select` background steady on hover, or give
|
|
175
|
+
it a stronger fill.
|
|
176
|
+
|
|
168
177
|
## Notes
|
|
169
178
|
|
|
170
179
|
- `required` is enough on native inputs. `aria-required="true"` is redundant and not used in these examples.
|
|
@@ -175,7 +184,7 @@ A full form using the control classes, field wrappers, and form actions:
|
|
|
175
184
|
- Use native `<optgroup>` and `disabled` options in `.select` controls when applicable; the customizable picker preserves both as a progressive enhancement.
|
|
176
185
|
- `.choice` is the choice-label API. Use `.check` or `.radio` on the nested control. The control goes first, the label group second, so multi-line labels align the control with the first line.
|
|
177
186
|
- For a switch, use `class="switch"` and `role="switch"`.
|
|
178
|
-
-
|
|
187
|
+
- `.range` skins the native range control (`appearance: none` track, thumb, and focus ring). Add `data-enhance="range"` for the fill share, named `datalist` values, and output sync.
|
|
179
188
|
- `.form-actions` carries a default top margin. Override it with `--form-actions-margin-block-start`, `--form-actions-align`, or `--form-actions-justify`. It is class-only and may live inside or outside `<form>`. See Detached Actions below.
|
|
180
189
|
- `.join` visually joins adjacent controls into a single unit. Use `.join-addon` for static prefix/suffix content. The container still carries `role="group"` for accessibility.
|
|
181
190
|
|
|
@@ -12,14 +12,117 @@ rather than the CSS box.
|
|
|
12
12
|
```html demo
|
|
13
13
|
<label class="field">
|
|
14
14
|
<span class="field-label">Volume</span>
|
|
15
|
-
<input class="range" type="range" min="0" max="100" value="50" />
|
|
15
|
+
<input class="range" data-enhance="range" type="range" min="0" max="100" value="50" />
|
|
16
16
|
</label>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
## Fill
|
|
20
|
+
|
|
21
|
+
`data-enhance="range"` opts into the `actual-css/js/range` behavior. It writes
|
|
22
|
+
`--range-progress`, the fill share the track gradient reads, and keeps it in
|
|
23
|
+
sync on input and after a form reset, including inputs associated through
|
|
24
|
+
`form="…"`. Without it the variable stays at its `0%` default and the track
|
|
25
|
+
renders plain — the control stays fully usable without JavaScript. The fill
|
|
26
|
+
follows the writing direction, and a bare `input[type="range"]` without the
|
|
27
|
+
`.range` class can opt in the same way.
|
|
28
|
+
|
|
29
|
+
## Named values
|
|
30
|
+
|
|
31
|
+
A `datalist` is the source of truth for discrete named steps: positions plus
|
|
32
|
+
names. Browsers render its ticks and labels inconsistently, so Actual never
|
|
33
|
+
depends on their presentation — the enhancement only reads `option` values and
|
|
34
|
+
their `label`. A matching label sets `aria-valuetext` on the input and mirrors
|
|
35
|
+
into each `<output for="…">`. The output is the visible value, not the input's
|
|
36
|
+
name — give the input its own accessible name, since `for` only declares the
|
|
37
|
+
output's operands.
|
|
38
|
+
|
|
39
|
+
```html demo
|
|
40
|
+
<div class="field">
|
|
41
|
+
<output class="field-label" for="effort">High</output>
|
|
42
|
+
<input
|
|
43
|
+
class="range"
|
|
44
|
+
data-enhance="range"
|
|
45
|
+
id="effort"
|
|
46
|
+
type="range"
|
|
47
|
+
min="0"
|
|
48
|
+
max="3"
|
|
49
|
+
step="1"
|
|
50
|
+
value="2"
|
|
51
|
+
list="effort-levels"
|
|
52
|
+
aria-label="Effort"
|
|
53
|
+
/>
|
|
54
|
+
<datalist id="effort-levels">
|
|
55
|
+
<option value="0" label="Minimal"></option>
|
|
56
|
+
<option value="1" label="Standard"></option>
|
|
57
|
+
<option value="2" label="High"></option>
|
|
58
|
+
<option value="3" label="Maximum"></option>
|
|
59
|
+
</datalist>
|
|
60
|
+
</div>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`aria-valuetext` is the announced value when a number really means a text; keep
|
|
64
|
+
it even if the visible `output` changes shape. An `output` is a live region,
|
|
65
|
+
so verify with a screen reader that the two do not double-announce.
|
|
66
|
+
|
|
67
|
+
## In a flyout
|
|
68
|
+
|
|
69
|
+
The range stays an `input`, the flyout stays a flyout — together they read as
|
|
70
|
+
a richer picker with no new component. `data-flyout-auto-close="outside"` keeps
|
|
71
|
+
dragging the slider from closing the panel.
|
|
72
|
+
|
|
73
|
+
```html demo
|
|
74
|
+
<div class="flyout-trigger">
|
|
75
|
+
<button
|
|
76
|
+
class="btn outline"
|
|
77
|
+
type="button"
|
|
78
|
+
data-enhance="flyout"
|
|
79
|
+
aria-expanded="false"
|
|
80
|
+
aria-controls="effort-picker"
|
|
81
|
+
>
|
|
82
|
+
Reasoning effort
|
|
83
|
+
</button>
|
|
84
|
+
|
|
85
|
+
<div
|
|
86
|
+
class="flyout"
|
|
87
|
+
id="effort-picker"
|
|
88
|
+
data-flyout-auto-close="outside"
|
|
89
|
+
style="--flyout-inline-size: 16rem; --flyout-pad: var(--space-40)"
|
|
90
|
+
hidden
|
|
91
|
+
>
|
|
92
|
+
<div class="field">
|
|
93
|
+
<output class="field-label text-center" for="effort-flyout">High</output>
|
|
94
|
+
|
|
95
|
+
<input
|
|
96
|
+
class="range"
|
|
97
|
+
data-enhance="range"
|
|
98
|
+
id="effort-flyout"
|
|
99
|
+
type="range"
|
|
100
|
+
min="0"
|
|
101
|
+
max="3"
|
|
102
|
+
step="1"
|
|
103
|
+
value="2"
|
|
104
|
+
list="effort-flyout-levels"
|
|
105
|
+
aria-label="Reasoning effort"
|
|
106
|
+
>
|
|
107
|
+
|
|
108
|
+
<datalist id="effort-flyout-levels">
|
|
109
|
+
<option value="0" label="Minimal"></option>
|
|
110
|
+
<option value="1" label="Standard"></option>
|
|
111
|
+
<option value="2" label="High"></option>
|
|
112
|
+
<option value="3" label="Maximum"></option>
|
|
113
|
+
</datalist>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="cluster" style="--cluster-justify: center">
|
|
117
|
+
<button class="btn primary sm" type="button" data-flyout-close>Done</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Actual deliberately does not emulate tick marks or multi-thumb ranges. Ticks
|
|
124
|
+
still need engine-specific pseudo-elements; the fill above is designed for
|
|
125
|
+
deletion once `::slider-fill` is interoperable — see
|
|
23
126
|
[Enhanced range controls](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/platform-alignment.md).
|
|
24
127
|
|
|
25
128
|
## CSS hooks
|
|
@@ -28,3 +131,5 @@ styling becomes interoperable — see
|
|
|
28
131
|
- `--range-track-height` — track thickness.
|
|
29
132
|
- `--range-thumb-bg` — thumb color. Defaults to the local intent, then `--primary`.
|
|
30
133
|
- `--range-track-bg` — track color.
|
|
134
|
+
- `--range-fill-bg` — filled portion color. Defaults to the local intent, then `--primary`.
|
|
135
|
+
- `--range-progress` — fill share. Written by `actual-css/js/range`, never set by hand.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Rich text
|
|
2
|
+
|
|
3
|
+
> A rich-text editor is not shipped by the framework — the native `<textarea>` stays the baseline. Enhance it with a dedicated engine, themed with Actual tokens.
|
|
4
|
+
|
|
5
|
+
Use a `<textarea>` while editing text, and a hosted editing engine as soon as
|
|
6
|
+
editing means a document (formatting, lists, links, mentions). The
|
|
7
|
+
[rich-text template](../../demo/templates/rich-text.html) enhances a textarea
|
|
8
|
+
with [@lekoala/rich-text](https://github.com/lekoala/rich-text) (Squire
|
|
9
|
+
underneath): the package generates the toolbar with its roving tabindex,
|
|
10
|
+
states, commands and lifecycle, while Actual maps its visual language onto the
|
|
11
|
+
package's `--rt-*` tokens. No adapter, no compatibility layer.
|
|
12
|
+
|
|
13
|
+
**Related terms:** rich text, richtext, WYSIWYG, HTML editor, formatted text, Squire, ProseMirror, Tiptap, text formatting, bold italic, mentions, slash commands.
|
|
14
|
+
|
|
15
|
+
## Theme bridge
|
|
16
|
+
|
|
17
|
+
The template loads the package JS and CSS from a CDN (`@0.2` caret range) and
|
|
18
|
+
declares one editor per field — Actual never builds toolbar buttons or calls
|
|
19
|
+
into the editing engine:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<div class="field actual-rich-text">
|
|
23
|
+
<label class="field-label" for="note">Team note</label>
|
|
24
|
+
<rich-text id="note-editor">
|
|
25
|
+
<textarea class="textarea" id="note" name="note" required placeholder="Write a note…"></textarea>
|
|
26
|
+
</rich-text>
|
|
27
|
+
<span class="field-help">Bold, italic, lists, quotes and links.</span>
|
|
28
|
+
</div>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
editor.options = {
|
|
33
|
+
buttons, // Tabler icons as Nodes, never HTML strings
|
|
34
|
+
requestLink, // an Actual <dialog> replacing window.prompt()
|
|
35
|
+
suggestions, // static @people and /snippets providers
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The bridge maps tokens; it does not restyle the editor's internals:
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
.actual-rich-text .rt-shell {
|
|
43
|
+
--rt-bg: var(--surface);
|
|
44
|
+
--rt-fg: var(--text);
|
|
45
|
+
--rt-border: var(--form-invalid-border, var(--control-border, var(--border)));
|
|
46
|
+
--rt-divider: var(--border);
|
|
47
|
+
--rt-focus: var(--form-invalid-border, var(--focus));
|
|
48
|
+
--rt-toolbar-bg: var(--surface-subtle);
|
|
49
|
+
--rt-button-pressed-bg: var(--state-selected);
|
|
50
|
+
--rt-link: var(--primary);
|
|
51
|
+
--rt-suggestion-bg: var(--surface-raised);
|
|
52
|
+
--rt-suggestion-shadow: var(--shadow-popout);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The card in the template documents the full token map of this recipe. The
|
|
57
|
+
suggestion popover is a child of `.rt-shell`, so it inherits the tokens like
|
|
58
|
+
the rest of the editor. `--rt-border` paints only the field frame, so the
|
|
59
|
+
invalid and hover hooks never reach the toolbar separators or the quote bar,
|
|
60
|
+
which follow `--rt-divider`.
|
|
61
|
+
|
|
62
|
+
## Bridge contract
|
|
63
|
+
|
|
64
|
+
`@lekoala/rich-text` owns the editor UI semantics — progressive enhancement,
|
|
65
|
+
sanitization, toolbar behaviour and keyboard, links, `@mentions`, `/`
|
|
66
|
+
suggestions, form/reset/readonly/disabled integration, selection and undo
|
|
67
|
+
lifecycle. Actual owns presentation and composer chrome — tokens, visual
|
|
68
|
+
states, dialogs, attachments, send, `Ctrl/Cmd+Enter`.
|
|
69
|
+
|
|
70
|
+
Formatting buttons stay local to the editor instance. The stateless `command`
|
|
71
|
+
router never drives bold/italic: those depend on selection, schema and history,
|
|
72
|
+
which only the engine knows. Reserve `command` for the chrome around the
|
|
73
|
+
editor (attach, template, dictate, send).
|
|
74
|
+
|
|
75
|
+
A rich composer reuses `.composer` and `.composer-actions` for the outer
|
|
76
|
+
surface; the bridge only lifts the editor and toolbar into the composer grid
|
|
77
|
+
with `display: contents` and grid areas. `composer.css` itself never knows
|
|
78
|
+
about `.rt-*`. The plain-text [composer template](../../demo/templates/composer.html)
|
|
79
|
+
stays the canonical pattern when editing means text, not a document.
|
|
80
|
+
|
|
81
|
+
People lookup and templates are application concerns: the demo wires static
|
|
82
|
+
arrays into the package's suggestion seam, and the composer buttons type
|
|
83
|
+
`@`/`/` so the providers do the rest. Attachments stay composer-owned —
|
|
84
|
+
logged, never serialized into the HTML.
|
|
85
|
+
|
|
86
|
+
## Storage and sanitization
|
|
87
|
+
|
|
88
|
+
The form submits limited HTML, not engine state: portable, readable,
|
|
89
|
+
editor-independent, reversible. Sanitization is mandatory inside the package
|
|
90
|
+
and not configurable; the client-side sanitizer is a UI boundary, not the
|
|
91
|
+
persistence security boundary — sanitize again on the server before storing or
|
|
92
|
+
rendering untrusted content. An editor holding only the empty block serializes
|
|
93
|
+
to `""`, so native `required` keeps working.
|
|
94
|
+
|
|
95
|
+
If the product later needs truly structural content (custom document nodes,
|
|
96
|
+
annotations, collaboration), a schema-based engine becomes interesting — see
|
|
97
|
+
the [rich-text design note](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/rich-text.md).
|
|
98
|
+
|
|
99
|
+
## Baseline traps
|
|
100
|
+
|
|
101
|
+
The textarea is the value owner and the no-JS fallback, holding raw HTML. Two
|
|
102
|
+
details keep it honest:
|
|
103
|
+
|
|
104
|
+
- Without the integration script the page remains a plain textarea — the link
|
|
105
|
+
dialog wiring and the suggestion providers only exist once the package
|
|
106
|
+
loads, and the composer falls back to plain-text send.
|
|
107
|
+
- `required` cannot sit usefully on a hidden textarea: mirror validity with
|
|
108
|
+
`aria-invalid` on submit (the package emits `richtext:invalid` and focuses
|
|
109
|
+
the editor when it is the form's first invalid control), and let the bridge
|
|
110
|
+
re-own the danger hook from there.
|
|
@@ -55,7 +55,11 @@ Native validation first; the enhancer only adds state and focus behavior.
|
|
|
55
55
|
|
|
56
56
|
Actual CSS ships validation *styles*. Invalid fields are marked with `aria-invalid="true"` (or the manual `.field.danger` wrapper class). The default forms bundle imports `forms/validation.css`; custom builds may omit that file when validation styling is app-owned. A small enhancer (`actual-css/js/validation`), included in the full runtime (`actual-css/js/full`) and also importable on its own, prevents premature error display, marks invalid fields on blur and submit, focuses the first invalid field on submit, and supports a few custom rules. It is not a validation framework — server and AJAX validation stay in app code.
|
|
57
57
|
|
|
58
|
-
Opt in with
|
|
58
|
+
Opt in with `data-enhance="validation"` on the form; `.needs-validation` alone
|
|
59
|
+
only enables the CSS feedback. Importing the module registers the behavior;
|
|
60
|
+
there is no init call. To opt in forms you cannot mark up, call
|
|
61
|
+
`applyEnhancement("validation", "form.needs-validation")` from
|
|
62
|
+
`actual-css/js/enhance`.
|
|
59
63
|
|
|
60
64
|
```html demo
|
|
61
65
|
<form class="needs-validation" data-enhance="validation" data-validation-message="Please check the highlighted fields.">
|