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
package/src/js/status.js
CHANGED
|
@@ -67,6 +67,21 @@ let statusTimer;
|
|
|
67
67
|
let statusGeneration = 0;
|
|
68
68
|
let viewportTracker = null;
|
|
69
69
|
const statusHomes = new WeakMap();
|
|
70
|
+
// Reference kept only while the bar is temporarily mounted in a dialog. If
|
|
71
|
+
// that dialog is removed from the DOM (htmx/Turbo swap), document lookup can
|
|
72
|
+
// no longer find the bar — this reference lets the next status()/clear()
|
|
73
|
+
// restore it to its home instead of silently no-opping forever.
|
|
74
|
+
let mountedStatusTarget = null;
|
|
75
|
+
|
|
76
|
+
function healDetachedStatusTarget() {
|
|
77
|
+
if (!mountedStatusTarget || mountedStatusTarget.isConnected) return;
|
|
78
|
+
if (!statusHomes.has(mountedStatusTarget)) {
|
|
79
|
+
mountedStatusTarget = null;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
restoreStatusTarget(mountedStatusTarget);
|
|
83
|
+
mountedStatusTarget = null;
|
|
84
|
+
}
|
|
70
85
|
|
|
71
86
|
function statusTarget() {
|
|
72
87
|
if (typeof document === "undefined") return null;
|
|
@@ -83,6 +98,7 @@ function restoreStatusTarget(target) {
|
|
|
83
98
|
parent.insertBefore(target, reference);
|
|
84
99
|
}
|
|
85
100
|
statusHomes.delete(target);
|
|
101
|
+
if (mountedStatusTarget === target) mountedStatusTarget = null;
|
|
86
102
|
}
|
|
87
103
|
|
|
88
104
|
function mountStatusTarget(target, source) {
|
|
@@ -97,6 +113,7 @@ function mountStatusTarget(target, source) {
|
|
|
97
113
|
statusHomes.set(target, { parent: target.parentNode, nextSibling: target.nextSibling });
|
|
98
114
|
}
|
|
99
115
|
dialog.append(target);
|
|
116
|
+
mountedStatusTarget = target;
|
|
100
117
|
}
|
|
101
118
|
|
|
102
119
|
// The bar is fixed to the layout viewport, so the mobile software keyboard
|
|
@@ -149,6 +166,7 @@ function clearIntentClasses(target) {
|
|
|
149
166
|
}
|
|
150
167
|
|
|
151
168
|
export function status(message, options = {}) {
|
|
169
|
+
healDetachedStatusTarget();
|
|
152
170
|
const target = statusTarget();
|
|
153
171
|
if (!target || message == null) return;
|
|
154
172
|
|
|
@@ -186,6 +204,7 @@ export function status(message, options = {}) {
|
|
|
186
204
|
|
|
187
205
|
status.clear = function clear() {
|
|
188
206
|
clearTimeout(statusTimer);
|
|
207
|
+
healDetachedStatusTarget();
|
|
189
208
|
|
|
190
209
|
const target = statusTarget();
|
|
191
210
|
if (!target) {
|
|
@@ -228,10 +247,6 @@ function resolveStatusTrigger(trigger) {
|
|
|
228
247
|
return target && target === statusTarget() ? target : null;
|
|
229
248
|
}
|
|
230
249
|
|
|
231
|
-
function connectStatusTrigger(trigger, target) {
|
|
232
|
-
trigger.setAttribute("aria-controls", target.id);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
250
|
if (typeof document !== "undefined") {
|
|
236
251
|
document.addEventListener(EVENTS.invalid, (event) => {
|
|
237
252
|
const message = event.detail?.message;
|
|
@@ -254,9 +269,7 @@ if (typeof document !== "undefined") {
|
|
|
254
269
|
|
|
255
270
|
registerCommands(STATUS_SHOW_COMMANDS, {
|
|
256
271
|
resolve: resolveStatusTrigger,
|
|
257
|
-
|
|
258
|
-
handle: (event, trigger) => {
|
|
259
|
-
event.preventDefault();
|
|
272
|
+
handle: (_event, trigger) => {
|
|
260
273
|
dispatchStatusEvent(trigger, {
|
|
261
274
|
message: trigger.getAttribute("data-status-message"),
|
|
262
275
|
intent: trigger.getAttribute("data-status-intent") ?? undefined,
|
|
@@ -267,9 +280,7 @@ if (typeof document !== "undefined") {
|
|
|
267
280
|
|
|
268
281
|
registerCommands(STATUS_CLEAR_COMMANDS, {
|
|
269
282
|
resolve: resolveStatusTrigger,
|
|
270
|
-
|
|
271
|
-
handle: (event, trigger) => {
|
|
272
|
-
event.preventDefault();
|
|
283
|
+
handle: (_event, trigger) => {
|
|
273
284
|
dispatchStatusEvent(trigger, {});
|
|
274
285
|
},
|
|
275
286
|
});
|
package/src/js/surface.js
CHANGED
|
@@ -222,7 +222,10 @@ function startSurfaceResources(menu, state) {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
export function isSurfaceOpen(menu) {
|
|
225
|
-
|
|
225
|
+
// The open set is the source of truth; .is-open is only its visual
|
|
226
|
+
// projection. A morph touching classes must not orphan Escape entries,
|
|
227
|
+
// position tracking, or the open set.
|
|
228
|
+
return openSurfaces.has(menu);
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
export function prepareSurface(menu) {
|
|
@@ -304,8 +307,14 @@ export function openSurface(menu, opts = {}) {
|
|
|
304
307
|
state.coordinateSpace = mode.space;
|
|
305
308
|
menu.style.position = mode.position;
|
|
306
309
|
|
|
310
|
+
// Register before promoting: a position failure below closes the surface
|
|
311
|
+
// again, and that close must observe the open state to tear down.
|
|
312
|
+
openSurfaces.add(menu);
|
|
307
313
|
// Promote before measuring: a closed popover has no box to position.
|
|
308
|
-
if (!showTransport(menu))
|
|
314
|
+
if (!showTransport(menu)) {
|
|
315
|
+
openSurfaces.delete(menu);
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
309
318
|
menu.classList.add(CLASSES.open);
|
|
310
319
|
syncExpanded(menu, true);
|
|
311
320
|
|
|
@@ -335,13 +344,12 @@ export function openSurface(menu, opts = {}) {
|
|
|
335
344
|
* from; placing focus in the new surface is the caller's job, and both
|
|
336
345
|
* callers in tree do it as soon as this returns.
|
|
337
346
|
*/
|
|
338
|
-
for (const other of openSurfaces) {
|
|
347
|
+
for (const other of [...openSurfaces]) {
|
|
339
348
|
if (other !== menu && other.ownerDocument === menu.ownerDocument) {
|
|
340
349
|
closeSurface(other, { restoreFocus: false });
|
|
341
350
|
}
|
|
342
351
|
}
|
|
343
352
|
|
|
344
|
-
openSurfaces.add(menu);
|
|
345
353
|
startSurfaceResources(menu, state);
|
|
346
354
|
return true;
|
|
347
355
|
}
|
|
@@ -411,7 +419,3 @@ function ensureDocumentClick(menu) {
|
|
|
411
419
|
clickBoundDocuments.add(doc);
|
|
412
420
|
doc.addEventListener("click", onDocumentClick);
|
|
413
421
|
}
|
|
414
|
-
|
|
415
|
-
export function getSurfaceAutoClose(menu) {
|
|
416
|
-
return surfaceMap.get(menu)?.autoClose ?? "outside";
|
|
417
|
-
}
|
package/src/js/tab.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { registerEnhancement } from "./enhance.js";
|
|
20
|
-
import { itemForKey } from "./keys.js";
|
|
20
|
+
import { itemForKey, shouldIgnoreKey } from "./keys.js";
|
|
21
21
|
|
|
22
22
|
const TABLIST_SELECTOR = '[data-enhance~="tabs"]';
|
|
23
23
|
|
|
@@ -100,6 +100,7 @@ function initialize(list) {
|
|
|
100
100
|
|
|
101
101
|
function onKeydown(e) {
|
|
102
102
|
const list = e.currentTarget;
|
|
103
|
+
if (shouldIgnoreKey(e)) return;
|
|
103
104
|
const tab = e.target.closest('[role="tab"]');
|
|
104
105
|
if (!tab || tab.closest(TABLIST_SELECTOR) !== list) return;
|
|
105
106
|
|
package/src/js/tooltip.js
CHANGED
|
@@ -23,7 +23,16 @@
|
|
|
23
23
|
* for tooltips that are never triggered. AJAX-loaded triggers
|
|
24
24
|
* work automatically via bubbling delegated listeners.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
26
|
+
* Snapshot, not reactive: generated tooltip text is synchronized when
|
|
27
|
+
* the tooltip is shown. Changes to data-tooltip do not rewrite
|
|
28
|
+
* a tooltip that is already shown. Always-visible generated
|
|
29
|
+
* tooltips therefore keep their initial text; use an explicit
|
|
30
|
+
* tooltip when its content must update dynamically.
|
|
31
|
+
*
|
|
32
|
+
* Show: hover + keyboard focus (150ms delay), click toggle, or always
|
|
33
|
+
* visible. Focus counts only when it matches :focus-visible, so
|
|
34
|
+
* a pointer press, or focus handed back by a closing dialog after
|
|
35
|
+
* a pointer close, shows nothing.
|
|
27
36
|
* Hide: focus leaving the trigger (focusout), pointer leave, Escape
|
|
28
37
|
*
|
|
29
38
|
* Geometry: a trigger scrolled out of the positioning boundary takes its tip
|
|
@@ -367,7 +376,10 @@ function ensureTip(trigger) {
|
|
|
367
376
|
const text = trigger.getAttribute("data-tooltip");
|
|
368
377
|
const doc = trigger.ownerDocument;
|
|
369
378
|
|
|
370
|
-
// shorthand: data-tooltip="text" → create element lazily
|
|
379
|
+
// shorthand: data-tooltip="text" → create element lazily.
|
|
380
|
+
// Shorthand content is always text. Never use innerHTML here:
|
|
381
|
+
// data-tooltip may hold application/user-provided strings. Rich content
|
|
382
|
+
// belongs in an explicit author-owned tooltip.
|
|
371
383
|
if (text) {
|
|
372
384
|
tip = doc.createElement("div");
|
|
373
385
|
tip.className = CLASSES.tooltip;
|
|
@@ -467,6 +479,14 @@ function show(tip, ref, immediate = false) {
|
|
|
467
479
|
const state = tipStates.get(tip);
|
|
468
480
|
if (!state) return;
|
|
469
481
|
|
|
482
|
+
// Shorthand tips copy data-tooltip text at creation; re-read it on every
|
|
483
|
+
// show so a "Copy" → "Copied!" update is reflected without rewiring.
|
|
484
|
+
// Text only, never innerHTML — see the note at creation above.
|
|
485
|
+
if (state.generated) {
|
|
486
|
+
const text = ref.getAttribute("data-tooltip");
|
|
487
|
+
if (text && tip.textContent !== text) tip.textContent = text;
|
|
488
|
+
}
|
|
489
|
+
|
|
470
490
|
state.activeRef = ref;
|
|
471
491
|
const mode = positionModeFor(ref);
|
|
472
492
|
state.space = mode.space;
|
|
@@ -502,6 +522,13 @@ function handleTriggerIntent(e) {
|
|
|
502
522
|
)
|
|
503
523
|
return;
|
|
504
524
|
|
|
525
|
+
// why: focus is a show intent only when it is keyboard focus. A pointer
|
|
526
|
+
// press focuses the trigger too, and a dialog or drawer closed with the
|
|
527
|
+
// pointer hands focus back to its invoker; neither asks for a label the
|
|
528
|
+
// user just acted on. :focus-visible is the browser's own modality verdict,
|
|
529
|
+
// the one the focus rings already follow, so Tab still shows the tip.
|
|
530
|
+
if (e.type === "focusin" && !e.target.matches(":focus-visible")) return;
|
|
531
|
+
|
|
505
532
|
const tip = ensureTip(trigger);
|
|
506
533
|
if (!tip) return;
|
|
507
534
|
|
package/src/js/validation.js
CHANGED
|
@@ -11,19 +11,20 @@
|
|
|
11
11
|
* Opt in with data-enhance="validation". The .needs-validation class
|
|
12
12
|
* remains valid for presentation-only (CSS :user-invalid feedback without
|
|
13
13
|
* the JS behavior). Importing the module registers the behavior; there is
|
|
14
|
-
* no init call.
|
|
14
|
+
* no init call. To opt existing forms in by selector, use
|
|
15
|
+
* applyEnhancement("validation", selector) so they join the enhance()
|
|
16
|
+
* lifecycle (inserted forms connect, removed forms tear down).
|
|
15
17
|
*
|
|
16
18
|
* The marker is a static, init-time contract. Removing the form from the
|
|
17
19
|
* DOM tears down everything, including the managed novalidate attribute.
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
|
-
import
|
|
22
|
+
import { registerEnhancement } from "./enhance.js";
|
|
21
23
|
import { EVENTS } from "./events.js";
|
|
22
24
|
import { CLASSES } from "./selectors.js";
|
|
23
25
|
|
|
24
26
|
const NOVALIDATE = "novalidate";
|
|
25
27
|
const WAS_VALIDATED_CLASS = CLASSES.wasValidated;
|
|
26
|
-
const VALIDATION_SELECTOR = enhancementSelector("validation");
|
|
27
28
|
const FIELD_CLASS = CLASSES.field;
|
|
28
29
|
const DANGER_CLASS = CLASSES.danger;
|
|
29
30
|
const MANAGED_NOVALIDATE_ATTR = "validationManagedNovalidate";
|
|
@@ -398,13 +399,6 @@ export class FormValidator {
|
|
|
398
399
|
rules[name] = callback;
|
|
399
400
|
}
|
|
400
401
|
|
|
401
|
-
static init(selector = `form${VALIDATION_SELECTOR}`) {
|
|
402
|
-
if (typeof document === "undefined") return;
|
|
403
|
-
for (const form of document.querySelectorAll(selector)) {
|
|
404
|
-
if (isFormElement(form)) connectForm(form);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
402
|
static setFieldError(el, message) {
|
|
409
403
|
el.setCustomValidity(message || "invalid");
|
|
410
404
|
el.dataset.validationErrors = "server";
|
|
@@ -435,10 +429,9 @@ export class FormValidator {
|
|
|
435
429
|
}
|
|
436
430
|
}
|
|
437
431
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
432
|
+
// Registered by name, not through a bare enhance(), so applyEnhancement()
|
|
433
|
+
// refreshes it like the other token behaviors. The token only means
|
|
434
|
+
// something on a form.
|
|
435
|
+
registerEnhancement("validation", (el) => (isFormElement(el) ? connectForm(el) : undefined));
|
|
443
436
|
|
|
444
437
|
export default FormValidator;
|