@signal9/era-ui 2.14.0 → 2.16.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/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.js +2 -1
- package/dist/dev/audit/audits/_helpers.d.ts +9 -0
- package/dist/dev/audit/audits/_helpers.js +15 -1
- package/dist/dev/audit/audits/index.d.ts +2 -1
- package/dist/dev/audit/audits/index.js +3 -1
- package/dist/dev/audit/audits/radius-concentricity.d.ts +2 -0
- package/dist/dev/audit/audits/radius-concentricity.js +52 -0
- package/dist/dev/index.d.ts +1 -0
- package/dist/dev/index.js +1 -0
- package/dist/dev/measure.d.ts +75 -0
- package/dist/dev/measure.js +142 -0
- package/dist/era-ui.css +1 -1
- package/dist/generated-docs/copy-button.md +25 -0
- package/dist/generated-docs/llms-full.txt +38 -0
- package/dist/generated-docs/llms.txt +1 -1
- package/dist/generated-docs/manifest.json +19 -0
- package/dist/generated-docs/measurements.md +3 -0
- package/dist/ui/code-block/code-block.svelte +7 -24
- package/dist/ui/copy-button/copy-button.svelte +85 -0
- package/dist/ui/copy-button/copy-button.svelte.d.ts +23 -0
- package/dist/ui/copy-button/index.d.ts +1 -0
- package/dist/ui/copy-button/index.js +1 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/package.json +1 -1
- package/dist/ai/copy-button.svelte +0 -42
- package/dist/ai/copy-button.svelte.d.ts +0 -8
package/dist/ai/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export { default as Checkpoint } from './checkpoint.svelte';
|
|
|
27
27
|
export { default as Loader } from './loader.svelte';
|
|
28
28
|
export { default as Shimmer } from './shimmer.svelte';
|
|
29
29
|
export { default as AIImage } from './image.svelte';
|
|
30
|
-
export {
|
|
30
|
+
export { CopyButton } from '../ui/copy-button/index.js';
|
|
31
31
|
export { StickToBottom } from './stick-to-bottom.svelte.js';
|
|
32
32
|
export type { PromptAttachment, PromptMessage, ChatStatus } from './prompt-input/index.js';
|
|
33
33
|
export type { MessageRole, MessageVersion } from './message/index.js';
|
package/dist/ai/index.js
CHANGED
|
@@ -32,7 +32,8 @@ export { default as Checkpoint } from './checkpoint.svelte';
|
|
|
32
32
|
export { default as Loader } from './loader.svelte';
|
|
33
33
|
export { default as Shimmer } from './shimmer.svelte';
|
|
34
34
|
export { default as AIImage } from './image.svelte';
|
|
35
|
-
|
|
35
|
+
// Promoted to a ui primitive; re-exported here so `$lib/ai`'s surface is stable.
|
|
36
|
+
export { CopyButton } from '../ui/copy-button/index.js';
|
|
36
37
|
export { StickToBottom } from './stick-to-bottom.svelte.js';
|
|
37
38
|
export { categorizeError } from './error-panel/index.js';
|
|
38
39
|
export { projectMessages, toolState } from './project.js';
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/** Sub-pixel tolerance. Below this, treat values as equal. */
|
|
2
2
|
export declare const TOL = 0.5;
|
|
3
|
+
/**
|
|
4
|
+
* The children that actually participate in the container's flex layout.
|
|
5
|
+
*
|
|
6
|
+
* Out-of-flow children (a slider thumb, a file-upload drop overlay, any
|
|
7
|
+
* absolutely-positioned decoration) are laid out against the container's padding
|
|
8
|
+
* box, not against their siblings — measuring them as flex items produces
|
|
9
|
+
* nonsense: overlapping boxes report NEGATIVE sibling gaps, which is how the
|
|
10
|
+
* slider reported a −87px gap and file-upload a −237px one. Skip them.
|
|
11
|
+
*/
|
|
3
12
|
export declare function flexChildren(el: HTMLElement): HTMLElement[];
|
|
4
13
|
export declare function isFlex(style: CSSStyleDeclaration): boolean;
|
|
5
14
|
export declare function isHorizontalFlex(style: CSSStyleDeclaration): boolean;
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
/** Sub-pixel tolerance. Below this, treat values as equal. */
|
|
2
2
|
export const TOL = 0.5;
|
|
3
|
+
/**
|
|
4
|
+
* The children that actually participate in the container's flex layout.
|
|
5
|
+
*
|
|
6
|
+
* Out-of-flow children (a slider thumb, a file-upload drop overlay, any
|
|
7
|
+
* absolutely-positioned decoration) are laid out against the container's padding
|
|
8
|
+
* box, not against their siblings — measuring them as flex items produces
|
|
9
|
+
* nonsense: overlapping boxes report NEGATIVE sibling gaps, which is how the
|
|
10
|
+
* slider reported a −87px gap and file-upload a −237px one. Skip them.
|
|
11
|
+
*/
|
|
3
12
|
export function flexChildren(el) {
|
|
4
|
-
return [...el.children].filter((c) =>
|
|
13
|
+
return [...el.children].filter((c) => {
|
|
14
|
+
if (!(c instanceof HTMLElement))
|
|
15
|
+
return false;
|
|
16
|
+
const pos = getComputedStyle(c).position;
|
|
17
|
+
return pos !== 'absolute' && pos !== 'fixed';
|
|
18
|
+
});
|
|
5
19
|
}
|
|
6
20
|
export function isFlex(style) {
|
|
7
21
|
return style.display === 'flex' || style.display === 'inline-flex';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { concentricInset } from './concentric-inset.js';
|
|
2
|
+
import { radiusConcentricity } from './radius-concentricity.js';
|
|
2
3
|
import { uniformSiblingGaps } from './uniform-sibling-gaps.js';
|
|
3
4
|
import { wallMatchesGap } from './wall-matches-gap.js';
|
|
4
|
-
export { concentricInset, uniformSiblingGaps, wallMatchesGap };
|
|
5
|
+
export { concentricInset, radiusConcentricity, uniformSiblingGaps, wallMatchesGap };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { registry } from '../registry.js';
|
|
2
2
|
import { concentricInset } from './concentric-inset.js';
|
|
3
|
+
import { radiusConcentricity } from './radius-concentricity.js';
|
|
3
4
|
import { uniformSiblingGaps } from './uniform-sibling-gaps.js';
|
|
4
5
|
import { wallMatchesGap } from './wall-matches-gap.js';
|
|
5
|
-
export { concentricInset, uniformSiblingGaps, wallMatchesGap };
|
|
6
|
+
export { concentricInset, radiusConcentricity, uniformSiblingGaps, wallMatchesGap };
|
|
6
7
|
// Auto-register built-ins. Consumers can still unregister or add their own.
|
|
7
8
|
registry.register(concentricInset);
|
|
9
|
+
registry.register(radiusConcentricity);
|
|
8
10
|
registry.register(uniformSiblingGaps);
|
|
9
11
|
registry.register(wallMatchesGap);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { TOL, flexChildren, isFlex } from './_helpers.js';
|
|
2
|
+
import { getConcentricity } from '../../measure.js';
|
|
3
|
+
/*
|
|
4
|
+
* The concentricity law: nested corners share a centre of curvature only when
|
|
5
|
+
* the radius step equals the gap. Stated as one identity —
|
|
6
|
+
*
|
|
7
|
+
* rd_outer − rd_inner == (h_outer − h_inner) / 2 == gap
|
|
8
|
+
*
|
|
9
|
+
* Checking any TWO of those three terms passes boxes that are visibly wrong, so
|
|
10
|
+
* this asserts all three together. Rounded containers only: on data-corners
|
|
11
|
+
* ="square" every radius collapses to 0 and the law is vacuously true.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Tallest a container can be and still be a bounded TIER control. The tallest
|
|
15
|
+
* tier is --era-h-lg, which peaks at 64px on touch; anything above that is an
|
|
16
|
+
* unbounded content container.
|
|
17
|
+
*/
|
|
18
|
+
const BOUNDED_MAX = 64;
|
|
19
|
+
export const radiusConcentricity = {
|
|
20
|
+
id: 'layout/radius-concentricity',
|
|
21
|
+
name: 'Concentric radius',
|
|
22
|
+
description: 'A rounded child in a rounded container must satisfy rd_outer − rd_inner = (h_outer − h_inner)/2 = gap, so the corners share a centre of curvature.',
|
|
23
|
+
category: 'layout',
|
|
24
|
+
severity: 'warn',
|
|
25
|
+
selector: '*',
|
|
26
|
+
check(el) {
|
|
27
|
+
const s = getComputedStyle(el);
|
|
28
|
+
if (!isFlex(s) || parseFloat(s.borderTopLeftRadius) <= 0)
|
|
29
|
+
return null;
|
|
30
|
+
// Bounded tier controls only. An unbounded container (card, pane, dialog
|
|
31
|
+
// body) is padded OPTICALLY (--era-pad-*), not by the even-gap law, so the
|
|
32
|
+
// radius step has nothing to be equal to — holding it to the identity is a
|
|
33
|
+
// false positive by the design system's own rules.
|
|
34
|
+
if (el.getBoundingClientRect().height > BOUNDED_MAX)
|
|
35
|
+
return null;
|
|
36
|
+
const issues = [];
|
|
37
|
+
for (const kid of flexChildren(el)) {
|
|
38
|
+
if (parseFloat(getComputedStyle(kid).borderTopLeftRadius) <= 0)
|
|
39
|
+
continue;
|
|
40
|
+
const c = getConcentricity(kid, el, TOL);
|
|
41
|
+
if (c.holds)
|
|
42
|
+
continue;
|
|
43
|
+
issues.push({
|
|
44
|
+
auditId: 'layout/radius-concentricity',
|
|
45
|
+
element: kid,
|
|
46
|
+
message: `radius step ${c.radiusDelta.toFixed(1)} ≠ implied gap ${c.impliedGap.toFixed(1)} ≠ measured gap ${c.gap.toFixed(1)}`,
|
|
47
|
+
details: { ...c }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return issues.length > 0 ? issues : null;
|
|
51
|
+
}
|
|
52
|
+
};
|
package/dist/dev/index.d.ts
CHANGED
package/dist/dev/index.js
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Sub-pixel residual to forgive — fractional line boxes and dpr rounding. */
|
|
2
|
+
export declare const TOL = 0.5;
|
|
3
|
+
export interface Box {
|
|
4
|
+
readonly x: number;
|
|
5
|
+
readonly y: number;
|
|
6
|
+
readonly width: number;
|
|
7
|
+
readonly height: number;
|
|
8
|
+
/** Computed top-left border-radius, in px (all four corners are equal in era). */
|
|
9
|
+
readonly radius: number;
|
|
10
|
+
}
|
|
11
|
+
export interface Gaps {
|
|
12
|
+
readonly top: number;
|
|
13
|
+
readonly right: number;
|
|
14
|
+
readonly bottom: number;
|
|
15
|
+
readonly left: number;
|
|
16
|
+
}
|
|
17
|
+
/** Rect + resolved radius, relative to the viewport. */
|
|
18
|
+
export declare function getRect(el: Element): Box;
|
|
19
|
+
/** Rect relative to an offset root, so overlays can draw in the root's space. */
|
|
20
|
+
export declare function getRelativeRect(el: Element, root: Element): Box;
|
|
21
|
+
/**
|
|
22
|
+
* The four gaps between a child's border box and its container's border box —
|
|
23
|
+
* what the eye actually reads as the inset, padding and border included.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getGaps(child: Element, container: Element): Gaps;
|
|
26
|
+
/** Shortest edge-to-edge distance between two boxes; 0 when they intersect. */
|
|
27
|
+
export declare function getDistance(a: Element, b: Element): number;
|
|
28
|
+
export declare function isEven(g: Gaps, tol?: number): boolean;
|
|
29
|
+
export interface Concentricity {
|
|
30
|
+
readonly outerRadius: number;
|
|
31
|
+
readonly innerRadius: number;
|
|
32
|
+
/** rd_outer − rd_inner */
|
|
33
|
+
readonly radiusDelta: number;
|
|
34
|
+
/** (h_outer − h_inner) / 2 — the vertical gap the tiers imply. */
|
|
35
|
+
readonly impliedGap: number;
|
|
36
|
+
/** The gap actually measured on top. */
|
|
37
|
+
readonly gap: number;
|
|
38
|
+
/** The identity: rd_outer − rd_inner == (h_outer − h_inner)/2 == gap. */
|
|
39
|
+
readonly holds: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The concentricity law. Nested corners share a centre of curvature only when
|
|
43
|
+
* the radius step equals the gap — checking any two of the three terms passes
|
|
44
|
+
* boxes that are visibly wrong, so assert all three at once.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getConcentricity(child: Element, container: Element, tol?: number): Concentricity;
|
|
47
|
+
/**
|
|
48
|
+
* One control nested in one container — the unit the overlay puts numbers on.
|
|
49
|
+
*
|
|
50
|
+
* A readout is NOT a verdict. Whether a box is WRONG is the audits' call
|
|
51
|
+
* (`runAudits` in ./audit), because the laws are subtler than any single pair:
|
|
52
|
+
* in a row of three buttons the middle one's distance to the container's left
|
|
53
|
+
* wall is meaningless, so "is this child's left gap equal to its top gap?" is
|
|
54
|
+
* the wrong question to ask of it. The audits ask the right ones — vertical
|
|
55
|
+
* symmetry per child, wall-vs-gap on the row, uniform sibling gaps, concentric
|
|
56
|
+
* radius — and this type just carries the geometry to draw.
|
|
57
|
+
*/
|
|
58
|
+
export interface Readout {
|
|
59
|
+
readonly container: HTMLElement;
|
|
60
|
+
readonly child: HTMLElement;
|
|
61
|
+
readonly childBox: Box;
|
|
62
|
+
readonly gaps: Gaps;
|
|
63
|
+
/** Only meaningful when both boxes are rounded; null otherwise. */
|
|
64
|
+
readonly concentric: Concentricity | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Every container/child pair in a subtree worth putting a number on.
|
|
68
|
+
*
|
|
69
|
+
* The filter is what keeps this from drowning in noise: a pair qualifies only
|
|
70
|
+
* when the container is a CONTROL-shaped box — bounded height, and rounded or
|
|
71
|
+
* flex/grid — because the even-gap law is about the geometric padding inside a
|
|
72
|
+
* bounded tier, not the optical padding of an unbounded card. Layout wrappers (a
|
|
73
|
+
* section, a demo column) are skipped, and so is anything inside an <svg>.
|
|
74
|
+
*/
|
|
75
|
+
export declare function measureReadouts(root: HTMLElement, maxContainerHeight?: number): Readout[];
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Measurement primitives — the numbers era's layout rules are stated in.
|
|
3
|
+
*
|
|
4
|
+
* A class string is a claim; the rendered box is the fact. Every metric rule in
|
|
5
|
+
* the design system (the even-gap law, the concentricity law) is an assertion
|
|
6
|
+
* about geometry that only a real layout pass can settle, so this module reads
|
|
7
|
+
* it back off the DOM: rects, computed radii, and the gaps between a control and
|
|
8
|
+
* its container.
|
|
9
|
+
*
|
|
10
|
+
* The API is deliberately shaped like mezr's (getRect / getGaps / getDistance)
|
|
11
|
+
* so it can be swapped for that library, but it stays in-repo for two reasons a
|
|
12
|
+
* generic measurement library can't cover:
|
|
13
|
+
*
|
|
14
|
+
* - era's rules turn on border-RADIUS as much as on distance (the
|
|
15
|
+
* concentricity identity), which no DOM-distance library reports;
|
|
16
|
+
* - the readouts have to render as token-driven DOM so they inherit
|
|
17
|
+
* data-surface / data-mode / data-font like everything else. A canvas ruler
|
|
18
|
+
* (@scena/ruler) is blind to those axes.
|
|
19
|
+
*
|
|
20
|
+
* DOM-only: every function here reads layout, so it must run in the browser.
|
|
21
|
+
*/
|
|
22
|
+
/** Sub-pixel residual to forgive — fractional line boxes and dpr rounding. */
|
|
23
|
+
export const TOL = 0.5;
|
|
24
|
+
/** Rect + resolved radius, relative to the viewport. */
|
|
25
|
+
export function getRect(el) {
|
|
26
|
+
const r = el.getBoundingClientRect();
|
|
27
|
+
return {
|
|
28
|
+
x: r.x,
|
|
29
|
+
y: r.y,
|
|
30
|
+
width: r.width,
|
|
31
|
+
height: r.height,
|
|
32
|
+
radius: parseFloat(getComputedStyle(el).borderTopLeftRadius) || 0
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Rect relative to an offset root, so overlays can draw in the root's space. */
|
|
36
|
+
export function getRelativeRect(el, root) {
|
|
37
|
+
const b = getRect(el);
|
|
38
|
+
const r = root.getBoundingClientRect();
|
|
39
|
+
return { ...b, x: b.x - r.x, y: b.y - r.y };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The four gaps between a child's border box and its container's border box —
|
|
43
|
+
* what the eye actually reads as the inset, padding and border included.
|
|
44
|
+
*/
|
|
45
|
+
export function getGaps(child, container) {
|
|
46
|
+
const c = child.getBoundingClientRect();
|
|
47
|
+
const p = container.getBoundingClientRect();
|
|
48
|
+
return {
|
|
49
|
+
top: c.top - p.top,
|
|
50
|
+
right: p.right - c.right,
|
|
51
|
+
bottom: p.bottom - c.bottom,
|
|
52
|
+
left: c.left - p.left
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Shortest edge-to-edge distance between two boxes; 0 when they intersect. */
|
|
56
|
+
export function getDistance(a, b) {
|
|
57
|
+
const r1 = a.getBoundingClientRect();
|
|
58
|
+
const r2 = b.getBoundingClientRect();
|
|
59
|
+
const dx = Math.max(r2.left - r1.right, r1.left - r2.right, 0);
|
|
60
|
+
const dy = Math.max(r2.top - r1.bottom, r1.top - r2.bottom, 0);
|
|
61
|
+
return Math.hypot(dx, dy);
|
|
62
|
+
}
|
|
63
|
+
export function isEven(g, tol = TOL) {
|
|
64
|
+
const { top, right, bottom, left } = g;
|
|
65
|
+
return (Math.abs(top - bottom) <= tol && Math.abs(left - right) <= tol && Math.abs(top - left) <= tol);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The concentricity law. Nested corners share a centre of curvature only when
|
|
69
|
+
* the radius step equals the gap — checking any two of the three terms passes
|
|
70
|
+
* boxes that are visibly wrong, so assert all three at once.
|
|
71
|
+
*/
|
|
72
|
+
export function getConcentricity(child, container, tol = TOL) {
|
|
73
|
+
const inner = getRect(child);
|
|
74
|
+
const outer = getRect(container);
|
|
75
|
+
const gaps = getGaps(child, container);
|
|
76
|
+
const radiusDelta = outer.radius - inner.radius;
|
|
77
|
+
const impliedGap = (outer.height - inner.height) / 2;
|
|
78
|
+
return {
|
|
79
|
+
outerRadius: outer.radius,
|
|
80
|
+
innerRadius: inner.radius,
|
|
81
|
+
radiusDelta,
|
|
82
|
+
impliedGap,
|
|
83
|
+
gap: gaps.top,
|
|
84
|
+
holds: Math.abs(radiusDelta - impliedGap) <= tol &&
|
|
85
|
+
Math.abs(impliedGap - gaps.top) <= tol &&
|
|
86
|
+
Math.abs(radiusDelta - gaps.top) <= tol
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Every container/child pair in a subtree worth putting a number on.
|
|
91
|
+
*
|
|
92
|
+
* The filter is what keeps this from drowning in noise: a pair qualifies only
|
|
93
|
+
* when the container is a CONTROL-shaped box — bounded height, and rounded or
|
|
94
|
+
* flex/grid — because the even-gap law is about the geometric padding inside a
|
|
95
|
+
* bounded tier, not the optical padding of an unbounded card. Layout wrappers (a
|
|
96
|
+
* section, a demo column) are skipped, and so is anything inside an <svg>.
|
|
97
|
+
*/
|
|
98
|
+
export function measureReadouts(root, maxContainerHeight = 96) {
|
|
99
|
+
const out = [];
|
|
100
|
+
const isCandidate = (el) => {
|
|
101
|
+
if (el === root)
|
|
102
|
+
return false;
|
|
103
|
+
const r = el.getBoundingClientRect();
|
|
104
|
+
if (r.height === 0 || r.height > maxContainerHeight)
|
|
105
|
+
return false;
|
|
106
|
+
const s = getComputedStyle(el);
|
|
107
|
+
return parseFloat(s.borderTopLeftRadius) > 0 || /flex|grid/.test(s.display);
|
|
108
|
+
};
|
|
109
|
+
const push = (container, child) => {
|
|
110
|
+
const childBox = getRect(child);
|
|
111
|
+
if (childBox.width === 0 || childBox.height === 0)
|
|
112
|
+
return;
|
|
113
|
+
// An absolutely-positioned child is out of flow — its gaps say nothing
|
|
114
|
+
// about the container's padding.
|
|
115
|
+
if (getComputedStyle(child).position === 'absolute')
|
|
116
|
+
return;
|
|
117
|
+
const containerBox = getRect(container);
|
|
118
|
+
out.push({
|
|
119
|
+
container,
|
|
120
|
+
child: child,
|
|
121
|
+
childBox,
|
|
122
|
+
gaps: getGaps(child, container),
|
|
123
|
+
concentric: childBox.radius > 0 && containerBox.radius > 0 ? getConcentricity(child, container) : null
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const walk = (el) => {
|
|
127
|
+
for (const child of el.children) {
|
|
128
|
+
if (child instanceof SVGElement) {
|
|
129
|
+
if (isCandidate(el))
|
|
130
|
+
push(el, child); // never descend into svg internals
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (!(child instanceof HTMLElement))
|
|
134
|
+
continue;
|
|
135
|
+
if (isCandidate(el))
|
|
136
|
+
push(el, child);
|
|
137
|
+
walk(child);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
walk(root);
|
|
141
|
+
return out;
|
|
142
|
+
}
|