@signal9/era-ui 30.1.2 → 30.1.4
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 +12 -0
- package/dist/os/desktop.svelte +15 -3
- package/dist/os/pane.svelte +10 -1
- package/dist/os/pane.svelte.d.ts +1 -1
- package/dist/ui/button/button.svelte +46 -8
- package/dist/ui/button/button.svelte.d.ts +19 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [30.1.4](https://github.com/sig-nine/era-ui/compare/v30.1.3...v30.1.4) (2026-08-16)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* **button:** loading no longer changes the control's width ([7931edb](https://github.com/sig-nine/era-ui/commit/7931edbdb7cbcabf496586983836fc8bf1832e42))
|
|
6
|
+
|
|
7
|
+
## [30.1.3](https://github.com/sig-nine/era-ui/compare/v30.1.2...v30.1.3) (2026-08-16)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **os:** stop printing a framework warning into consumer consoles ([312e1d5](https://github.com/sig-nine/era-ui/commit/312e1d531f4df19443282917819700b66f37ccfe))
|
|
12
|
+
|
|
1
13
|
## [30.1.2](https://github.com/sig-nine/era-ui/compare/v30.1.1...v30.1.2) (2026-08-16)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
package/dist/os/desktop.svelte
CHANGED
|
@@ -162,9 +162,21 @@
|
|
|
162
162
|
<div class="absolute inset-0 isolate">
|
|
163
163
|
<!-- Only the active workspace paints; other workspaces' panes keep
|
|
164
164
|
their state untouched and simply aren't in the tree. Keyed by id, so
|
|
165
|
-
switching back remounts them exactly where they were.
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
switching back remounts them exactly where they were.
|
|
166
|
+
|
|
167
|
+
ITERATING pm.panes AND FILTERING HERE, rather than iterating the
|
|
168
|
+
`activePanes` derived, is what makes `bind:` below legal: a pane's
|
|
169
|
+
geometry is written by Pane.Root through two bindings, and Svelte's
|
|
170
|
+
ownership check wants that declared the whole way up — but you cannot
|
|
171
|
+
bind to an item of a DERIVED array. `pm.panes[i]` is the same object
|
|
172
|
+
the derived would have yielded, in the same order (activePanes is a
|
|
173
|
+
pure filter), and the {#if} unmounts exactly what the filter excluded.
|
|
174
|
+
Without this the shell logged ownership_invalid_binding twice per
|
|
175
|
+
pane into every consumer's dev console. -->
|
|
176
|
+
{#each pm.panes as pane, i (pane.id)}
|
|
177
|
+
{#if pane.workspaceId === pm.activeWorkspaceId}
|
|
178
|
+
<Pane bind:pane={pm.panes[i]} />
|
|
179
|
+
{/if}
|
|
168
180
|
{/each}
|
|
169
181
|
</div>
|
|
170
182
|
{@render children?.()}
|
package/dist/os/pane.svelte
CHANGED
|
@@ -8,7 +8,16 @@
|
|
|
8
8
|
import { cn } from '../utils/index.js';
|
|
9
9
|
import { getPaneManager, type AppPane } from './pane-manager.svelte.js';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// $bindable, though nothing here ever reassigns it. Pane.Root takes
|
|
12
|
+
// `position` and `size` with bind:, which WRITES to pane.pos / pane.size —
|
|
13
|
+
// state this component does not own. Svelte's dev-mode ownership check
|
|
14
|
+
// followed that write up the tree, found no declared binding, and logged
|
|
15
|
+
// ownership_invalid_binding on every pane mount: era's own shell printing
|
|
16
|
+
// two framework warnings per load into every consumer's dev console.
|
|
17
|
+
// Declaring the binding here and using it in desktop.svelte is Svelte's own
|
|
18
|
+
// remedy for the message; it costs nothing at runtime and says out loud
|
|
19
|
+
// what the component has always done.
|
|
20
|
+
let { pane = $bindable() }: { pane: AppPane } = $props();
|
|
12
21
|
|
|
13
22
|
const pm = getPaneManager();
|
|
14
23
|
const focused = $derived(pm.focusedId === pane.id);
|
package/dist/os/pane.svelte.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ import { type AppPane } from './pane-manager.svelte.js';
|
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
pane: AppPane;
|
|
5
5
|
};
|
|
6
|
-
declare const Pane: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
declare const Pane: import("svelte").Component<$$ComponentProps, {}, "pane">;
|
|
7
7
|
type Pane = ReturnType<typeof Pane>;
|
|
8
8
|
export default Pane;
|
|
@@ -28,9 +28,25 @@
|
|
|
28
28
|
active?: boolean;
|
|
29
29
|
icon?: boolean;
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* In flight: disables the button, sets `aria-busy`, and puts a spinner
|
|
32
|
+
* over the label.
|
|
33
|
+
*
|
|
34
|
+
* THE GEOMETRY DOES NOT MOVE. That is the requirement, not a detail. A
|
|
35
|
+
* button is loading because it was just CLICKED, so anything `loading`
|
|
36
|
+
* changes about the box moves the page under the pointer that is still
|
|
37
|
+
* on it — and a spinner PREPENDED to the label (what this used to do)
|
|
38
|
+
* costs an icon's width plus a gap, measured on a wrapping header row
|
|
39
|
+
* that reflowed everything beside it. A consumer hit exactly that and
|
|
40
|
+
* stopped using the prop, hand-rolling `disabled` + `aria-busy`
|
|
41
|
+
* instead, which is the report that produced this note.
|
|
42
|
+
*
|
|
43
|
+
* So the spinner is absolutely positioned and takes no part in the
|
|
44
|
+
* flex layout, and the label goes to `opacity-0` rather than being
|
|
45
|
+
* unmounted: the box that holds the width stays, and the text stays in
|
|
46
|
+
* the accessibility tree as the button's accessible name.
|
|
47
|
+
*
|
|
48
|
+
* The visible label is therefore HIDDEN in flight — the one thing this
|
|
49
|
+
* trades away, and the reason `loadingLabel` exists.
|
|
34
50
|
*/
|
|
35
51
|
loading?: boolean;
|
|
36
52
|
/** Screen-reader-only label announced while `loading` is true. */
|
|
@@ -109,7 +125,15 @@
|
|
|
109
125
|
aria-busy={loading || undefined}
|
|
110
126
|
data-state={active ? 'on' : undefined}
|
|
111
127
|
data-size={size}
|
|
112
|
-
class={cn(
|
|
128
|
+
class={cn(
|
|
129
|
+
buttonVariants({ variant, tone, size, icon, active }),
|
|
130
|
+
// Positioned only while loading — the spinner overlay below needs a
|
|
131
|
+
// containing block, and giving EVERY button one would change paint order
|
|
132
|
+
// against its non-positioned neighbours for a state most of them never
|
|
133
|
+
// enter.
|
|
134
|
+
loading && 'relative',
|
|
135
|
+
className
|
|
136
|
+
)}
|
|
113
137
|
{...restProps}
|
|
114
138
|
>
|
|
115
139
|
<!--
|
|
@@ -142,6 +166,13 @@
|
|
|
142
166
|
data-button-content
|
|
143
167
|
class={cn(
|
|
144
168
|
'inline-flex items-center justify-center [gap:inherit]',
|
|
169
|
+
// THE LABEL KEEPS ITS BOX AND LOSES ITS INK. opacity, not `hidden` or
|
|
170
|
+
// `invisible`: the box has to stay (it is what holds the width) and the
|
|
171
|
+
// text has to stay in the accessibility tree (it is the button's
|
|
172
|
+
// accessible name, and a control that loses its name mid-flight is worse
|
|
173
|
+
// than one that shifts). See the `loading` prop for why the spinner
|
|
174
|
+
// cannot simply sit beside it.
|
|
175
|
+
loading && 'opacity-0',
|
|
145
176
|
// A link is running text, so its wrapper flows inline and takes NO ink
|
|
146
177
|
// centring. era-label-ink blockifies the box and trims it to the
|
|
147
178
|
// typographic band — right for a label in a fixed-height control, and
|
|
@@ -151,10 +182,6 @@
|
|
|
151
182
|
variant === 'link' ? 'inline' : 'not-has-[svg]:era-label-ink'
|
|
152
183
|
)}
|
|
153
184
|
>
|
|
154
|
-
{#if loading}
|
|
155
|
-
<LoaderCircle class="size-icon shrink-0 animate-spin" aria-hidden="true" />
|
|
156
|
-
{#if loadingLabel}<span class="sr-only">{loadingLabel}</span>{/if}
|
|
157
|
-
{/if}
|
|
158
185
|
<!-- The label box exists ONLY when `lead`/`trail` are used. Rendering it
|
|
159
186
|
unconditionally changed the DOM for every legacy call site: a
|
|
160
187
|
`display:contents` span has no box, and the geometry audits walk real
|
|
@@ -171,4 +198,15 @@
|
|
|
171
198
|
{@render children?.()}
|
|
172
199
|
{/if}
|
|
173
200
|
</span>
|
|
201
|
+
<!--
|
|
202
|
+
The spinner is OUT OF FLOW, which is the whole point. Absolutely positioned
|
|
203
|
+
children take no part in flex layout, so the button's width, height, gap and
|
|
204
|
+
min-w are the same in flight as at rest.
|
|
205
|
+
-->
|
|
206
|
+
{#if loading}
|
|
207
|
+
<span class="absolute inset-0 flex items-center justify-center">
|
|
208
|
+
<LoaderCircle class="size-icon shrink-0 animate-spin" aria-hidden="true" />
|
|
209
|
+
</span>
|
|
210
|
+
{#if loadingLabel}<span class="sr-only">{loadingLabel}</span>{/if}
|
|
211
|
+
{/if}
|
|
174
212
|
</Button.Root>
|
|
@@ -7,9 +7,25 @@ type $$ComponentProps = Button.RootProps & {
|
|
|
7
7
|
active?: boolean;
|
|
8
8
|
icon?: boolean;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* In flight: disables the button, sets `aria-busy`, and puts a spinner
|
|
11
|
+
* over the label.
|
|
12
|
+
*
|
|
13
|
+
* THE GEOMETRY DOES NOT MOVE. That is the requirement, not a detail. A
|
|
14
|
+
* button is loading because it was just CLICKED, so anything `loading`
|
|
15
|
+
* changes about the box moves the page under the pointer that is still
|
|
16
|
+
* on it — and a spinner PREPENDED to the label (what this used to do)
|
|
17
|
+
* costs an icon's width plus a gap, measured on a wrapping header row
|
|
18
|
+
* that reflowed everything beside it. A consumer hit exactly that and
|
|
19
|
+
* stopped using the prop, hand-rolling `disabled` + `aria-busy`
|
|
20
|
+
* instead, which is the report that produced this note.
|
|
21
|
+
*
|
|
22
|
+
* So the spinner is absolutely positioned and takes no part in the
|
|
23
|
+
* flex layout, and the label goes to `opacity-0` rather than being
|
|
24
|
+
* unmounted: the box that holds the width stays, and the text stays in
|
|
25
|
+
* the accessibility tree as the button's accessible name.
|
|
26
|
+
*
|
|
27
|
+
* The visible label is therefore HIDDEN in flight — the one thing this
|
|
28
|
+
* trades away, and the reason `loadingLabel` exists.
|
|
13
29
|
*/
|
|
14
30
|
loading?: boolean;
|
|
15
31
|
/** Screen-reader-only label announced while `loading` is true. */
|