staffa 0.13.0 → 0.14.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.md +19 -16
- package/dist/components/dialog.js +3 -1
- package/dist/components/main.d.ts +37 -48
- package/dist/components/main.js +72 -63
- package/dist/components/menu.d.ts +8 -0
- package/dist/components/menu.js +48 -13
- package/dist/components/panels.d.ts +103 -82
- package/dist/components/panels.js +180 -227
- package/dist/components/select.js +4 -2
- package/dist/components/tabs.js +9 -5
- package/dist/components/toast.js +3 -1
- package/dist/components/tooltip.js +12 -5
- package/dist/core.d.ts +15 -0
- package/dist/core.js +17 -0
- package/dist/index.d.ts +1 -1
- package/dist/staffa.esm.js +1 -1
- package/dist/theme.d.ts +13 -65
- package/dist/theme.js +21 -8
- package/package.json +1 -1
- package/skill/FloatingMenuOptions.md +10 -0
- package/skill/MainOptions.md +35 -49
- package/skill/Panel.md +31 -32
- package/skill/PanelStack.md +4 -4
- package/skill/SKILL.md +29 -16
- package/skill/main.md +2 -1
- package/src/components/dialog.ts +3 -1
- package/src/components/main.ts +109 -110
- package/src/components/menu.ts +56 -12
- package/src/components/panels.ts +247 -287
- package/src/components/select.ts +4 -2
- package/src/components/tabs.ts +7 -3
- package/src/components/toast.ts +3 -1
- package/src/components/tooltip.ts +12 -5
- package/src/core.ts +19 -0
- package/src/index.ts +1 -1
- package/src/theme.ts +24 -9
package/src/components/select.ts
CHANGED
|
@@ -37,8 +37,10 @@ A.insertGlobalCss({
|
|
|
37
37
|
*/
|
|
38
38
|
export function select(opts: SelectOptions): void {
|
|
39
39
|
drawField(opts, (id, isInvalid) => {
|
|
40
|
-
A("div.s-select_wrap",
|
|
41
|
-
|
|
40
|
+
A("div.s-select_wrap", () => {
|
|
41
|
+
// On the <select> itself, like every other field control — an
|
|
42
|
+
// aria-label on the wrapper div would label nothing.
|
|
43
|
+
A("select.s-input", opts.inputAttrs, () => {
|
|
42
44
|
applyControlAttrs(opts, id, isInvalid);
|
|
43
45
|
|
|
44
46
|
A("change=", (e: Event) => {
|
package/src/components/tabs.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import A from "aberdeen";
|
|
2
|
-
import { type Bindable, type Slot, type Attributes, drawSlot, uniqueId } from "../core.js";
|
|
2
|
+
import { cssZoom, type Bindable, type Slot, type Attributes, drawSlot, uniqueId } from "../core.js";
|
|
3
3
|
import { mk } from "../icons-helpers.js";
|
|
4
4
|
|
|
5
5
|
/** A single tab definition. */
|
|
@@ -143,9 +143,13 @@ export function revealInStrip(el: HTMLElement): void {
|
|
|
143
143
|
// The overlays are 2.4em wide; clearing a little more than that keeps the
|
|
144
144
|
// revealed item from sitting right against one.
|
|
145
145
|
const pad = parseFloat(getComputedStyle(row).fontSize) * 2.6;
|
|
146
|
+
// The rects are in window coordinates while `scrollBy` counts in the row's
|
|
147
|
+
// own space; their difference scales over by the row's zoom (see `cssZoom`).
|
|
148
|
+
const z = cssZoom(row);
|
|
146
149
|
const box = el.getBoundingClientRect(), strip = row.getBoundingClientRect();
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
const left = (box.left - strip.left) / z, right = (box.right - strip.right) / z;
|
|
151
|
+
if (left < pad) row.scrollBy({ left: left - pad, behavior: "smooth" });
|
|
152
|
+
else if (right > -pad) row.scrollBy({ left: right + pad, behavior: "smooth" });
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
/**
|
package/src/components/toast.ts
CHANGED
|
@@ -32,7 +32,9 @@ A.insertGlobalCss({
|
|
|
32
32
|
".s-toasts":
|
|
33
33
|
"position:fixed bottom:$3 right:$3 z-index:400 " +
|
|
34
34
|
"display:flex flex-direction:column gap:$2 " +
|
|
35
|
-
|
|
35
|
+
// 90vw divided by --s-zoom: viewport units shrink with the page-fitting
|
|
36
|
+
// zoom (see `watchScale` in main.ts), and this cap means the window.
|
|
37
|
+
"pointer-events:none max-width:min(calc(90vw/var(--s-zoom,1)),24rem) w:24rem",
|
|
36
38
|
".s-toast": {
|
|
37
39
|
"&":
|
|
38
40
|
"display:flex align-items:flex-start gap:$2 " +
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import A from "aberdeen";
|
|
2
|
-
import { type Slot, type Attributes, drawSlot, mountPortal } from "../core.js";
|
|
2
|
+
import { cssZoom, type Slot, type Attributes, drawSlot, mountPortal } from "../core.js";
|
|
3
3
|
|
|
4
4
|
/** Options for {@link addTooltip}. */
|
|
5
5
|
export interface TooltipOptions {
|
|
@@ -39,10 +39,12 @@ if (typeof window !== "undefined") {
|
|
|
39
39
|
window.addEventListener("scroll", () => { $ttActive.value = undefined; }, { capture: true, passive: true });
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function computePos(rect: DOMRect, tipW: number, tipH: number, placement: string): { x: number; y: number } {
|
|
42
|
+
function computePos(rect: DOMRect, tipW: number, tipH: number, placement: string, zoom: number): { x: number; y: number } {
|
|
43
|
+
// `rect` is handed over already in the tip's own coordinate space; the
|
|
44
|
+
// window's size has to be brought into it too (see `cssZoom`).
|
|
43
45
|
const gap = 7;
|
|
44
|
-
const vw = window.innerWidth;
|
|
45
|
-
const vh = window.innerHeight;
|
|
46
|
+
const vw = window.innerWidth / zoom;
|
|
47
|
+
const vh = window.innerHeight / zoom;
|
|
46
48
|
let x = 0, y = 0;
|
|
47
49
|
|
|
48
50
|
if (placement === "bottom") {
|
|
@@ -96,7 +98,12 @@ mountPortal(() => {
|
|
|
96
98
|
|
|
97
99
|
requestAnimationFrame(() => {
|
|
98
100
|
if (!document.body.contains(tipEl)) return;
|
|
99
|
-
|
|
101
|
+
// The anchor's rect is in window coordinates; the left/top set below live
|
|
102
|
+
// in the tip's own space — scale it over before computing (see `cssZoom`).
|
|
103
|
+
const z = cssZoom(tipEl);
|
|
104
|
+
const r = anchor.getBoundingClientRect();
|
|
105
|
+
const rect = new DOMRect(r.x / z, r.y / z, r.width / z, r.height / z);
|
|
106
|
+
const { x, y } = computePos(rect, tipEl.offsetWidth, tipEl.offsetHeight, placement, z);
|
|
100
107
|
tipEl.style.left = x + "px";
|
|
101
108
|
tipEl.style.top = y + "px";
|
|
102
109
|
tipEl.style.visibility = "";
|
package/src/core.ts
CHANGED
|
@@ -62,6 +62,25 @@ export interface ContentOptions {
|
|
|
62
62
|
*/
|
|
63
63
|
export const NARROW_PX = 640;
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The narrowest layout the library is designed for. A panel must work at this
|
|
67
|
+
* width (panels.ts sizes its columns from it), and a window narrower than this
|
|
68
|
+
* is shown the 360px layout scaled down to fit rather than squeezed further
|
|
69
|
+
* (see `watchScale` in main.ts).
|
|
70
|
+
*/
|
|
71
|
+
export const MIN_PX = 360;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `el`'s effective CSS zoom: the factor between its own coordinate space and
|
|
75
|
+
* the window's — 1 wherever `zoom` isn't in play, or in browsers that predate
|
|
76
|
+
* it. A `getBoundingClientRect()` or pointer coordinate is in window space;
|
|
77
|
+
* divide it by this before using it as a length or scroll offset, which live
|
|
78
|
+
* in the element's own space. (`offsetWidth` and friends already do.)
|
|
79
|
+
*/
|
|
80
|
+
export function cssZoom(el: Element): number {
|
|
81
|
+
return (el as { currentCSSZoom?: number }).currentCSSZoom ?? 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
let idCounter = 0;
|
|
66
85
|
/** Generates a process-unique id, used to wire `<label for>` to its control. */
|
|
67
86
|
export function uniqueId(prefix = "s"): string {
|
package/src/index.ts
CHANGED
|
@@ -41,7 +41,7 @@ export { buttonGroup, type ButtonGroupOptions } from "./components/buttonGroup.j
|
|
|
41
41
|
export { checkbox, type CheckboxOptions } from "./components/checkbox.js";
|
|
42
42
|
export { form, type FormOptions } from "./components/form.js";
|
|
43
43
|
export { main, closeNav, type MainOptions } from "./components/main.js";
|
|
44
|
-
export { type PanelStack, type Panel, type Routes, type RouteHandler, type RouteTable, type AncestorsHandler, type AncestorTable, type PathParams, type SegParams } from "./components/panels.js";
|
|
44
|
+
export { type PanelStack, type Panel, type PanelSize, type Routes, type RouteHandler, type RouteTable, type AncestorsHandler, type AncestorTable, type PathParams, type SegParams } from "./components/panels.js";
|
|
45
45
|
export { menu, menuButton, showFloatingMenu, addContextMenu, isFloatingMenuOpen, closeFloatingMenu, type MenuListOptions, type MenuOptions, type MenuEntry, type MenuItem, type MenuSeparator, type FloatingMenuOptions, type ContextMenuOptions } from "./components/menu.js";
|
|
46
46
|
export { dialog, alert, confirm, prompt, isDialogOpen, type DialogOptions } from "./components/dialog.js";
|
|
47
47
|
export { select, type SelectOptions, type SelectOptionInput } from "./components/select.js";
|
package/src/theme.ts
CHANGED
|
@@ -59,16 +59,31 @@ import A from "aberdeen";
|
|
|
59
59
|
*/
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* The
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* used by panels.ts, where the routed columns have to visually *be* the
|
|
68
|
-
* surface around them.
|
|
62
|
+
* The subtle single-colour wash a surface is painted with, as a `background:`
|
|
63
|
+
* declaration, at the given angle. Shared constants rather than a CSS custom
|
|
64
|
+
* property, deliberately: `var()`s inside a custom property resolve where the
|
|
65
|
+
* property is *defined*, so a `--s-sheen` at `:root` would paint every surface
|
|
66
|
+
* with the page's wash instead of its own `$s-bg`'s.
|
|
69
67
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
const sheen = (angle: string) =>
|
|
69
|
+
`background: linear-gradient(${angle}, color-mix(in oklab, $s-bg, white 9%), color-mix(in oklab, $s-bg, black 9%));`;
|
|
70
|
+
|
|
71
|
+
/** The surface sheen: the wash every surface (and the page) is painted with. */
|
|
72
|
+
export const SURFACE_SHEEN = sheen("170deg");
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The same wash, straight down — for panels.ts, where the routed columns and
|
|
76
|
+
* the ground beside them have to look like one continuous surface.
|
|
77
|
+
*
|
|
78
|
+
* A gradient at an angle takes its extent from the box's *width* as well as its
|
|
79
|
+
* height, so a 430px column and the 1720px region behind it would paint
|
|
80
|
+
* different slices of the same wash and meet at a visible step. Straight down,
|
|
81
|
+
* the extent is the height alone — which every column shares exactly with the
|
|
82
|
+
* region (a column is `top:0 bottom:0` in it) and so with every other column.
|
|
83
|
+
* The 10° of tilt is worth losing there; it buys the one place in the app where
|
|
84
|
+
* boxes of *different widths* must be seamless.
|
|
85
|
+
*/
|
|
86
|
+
export const PANEL_SHEEN = sheen("180deg");
|
|
72
87
|
|
|
73
88
|
const STORAGE_KEY = "staffa:darkMode";
|
|
74
89
|
|