concertina 0.11.0 → 0.13.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 +57 -2
- package/dist/accordion.cjs +6 -5
- package/dist/accordion.js +1 -1
- package/dist/{chunk-4DDADLSW.js → chunk-OGJMPKZX.js} +6 -5
- package/dist/index.cjs +120 -80
- package/dist/index.d.cts +64 -6
- package/dist/index.d.ts +64 -6
- package/dist/index.js +90 -52
- package/dist/styles.css +6 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ It's not a state problem. It's a **structure problem.** The box changed size bec
|
|
|
26
26
|
|
|
27
27
|
Don't swap structures. Swap what's inside them.
|
|
28
28
|
|
|
29
|
-
Concertina gives you
|
|
29
|
+
Concertina gives you four high-level components: **Bellows**, **Hum**, **Overture**, and **Ensemble**. They handle the math so you can focus on the music. CSS is auto-injected on first render. No manual imports needed.
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
npm install concertina
|
|
@@ -188,18 +188,71 @@ import { Hum } from "concertina";
|
|
|
188
188
|
|
|
189
189
|
The `className` is passed through to the shimmer so `1lh` inherits the correct font metrics. The shimmer is exactly as tall as the text it replaces because `1lh` resolves to the element's computed line-height. Not font-size, not a token, not a guess.
|
|
190
190
|
|
|
191
|
+
#### Vamp: ambient loading for entire subtrees
|
|
192
|
+
|
|
193
|
+
When many Hum instances share the same loading state (e.g. every cell in a table), threading `loading` to each one is boilerplate. Wrap the subtree in `<Vamp>` and every nested `<Hum>` picks it up automatically.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { Vamp, Hum } from "concertina";
|
|
197
|
+
|
|
198
|
+
<Vamp loading={isLoading}>
|
|
199
|
+
<h2><Hum className="text-xl font-bold">{user?.name}</Hum></h2>
|
|
200
|
+
<p><Hum className="text-sm text-stone">{user?.email}</Hum></p>
|
|
201
|
+
<p><Hum className="text-sm">{user?.bio}</Hum></p>
|
|
202
|
+
</Vamp>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
No `loading` prop on any Hum. They all read from Vamp. An explicit `loading` prop on any individual Hum still overrides context.
|
|
206
|
+
|
|
207
|
+
Named after musical **vamping** — repeating a pattern while waiting for a cue.
|
|
208
|
+
|
|
191
209
|
#### Hum props
|
|
192
210
|
|
|
193
211
|
| Prop | Type | Default | Description |
|
|
194
212
|
|------|------|---------|-------------|
|
|
195
|
-
| `loading` | `boolean` | | Show shimmer (true) or children (false) |
|
|
213
|
+
| `loading` | `boolean` | Vamp context | Show shimmer (true) or children (false). Falls back to nearest `<Vamp>` when omitted. |
|
|
196
214
|
| `as` | `ElementType` | `"span"` | HTML element to render |
|
|
197
215
|
| `className` | `string` | | Applied to both shimmer and content states |
|
|
198
216
|
|
|
217
|
+
#### Vamp props
|
|
218
|
+
|
|
219
|
+
| Prop | Type | Description |
|
|
220
|
+
|------|------|-------------|
|
|
221
|
+
| `loading` | `boolean` | Whether the subtree is in a loading/warmup state |
|
|
222
|
+
| `children` | `ReactNode` | Content to wrap |
|
|
223
|
+
|
|
199
224
|
> `StableText` is an alias for `Hum`.
|
|
200
225
|
|
|
201
226
|
---
|
|
202
227
|
|
|
228
|
+
## Overture: temporal stability for arbitrary content
|
|
229
|
+
|
|
230
|
+
A card, table, or page loads from an API. You want shimmer bones during loading, a smooth fade-out when data arrives, and the container must never collapse during the swap. You don't have a flat list — you have complex, nested JSX.
|
|
231
|
+
|
|
232
|
+
Overture composes `Vamp` (ambient loading context) + `Gigbag` (size ratchet) + `useWarmupExit` (exit transition) into a single wrapper. Write one JSX tree for both states. Nested `<Hum>` instances read loading state from the Vamp context automatically.
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
import { Overture, Hum } from "concertina";
|
|
236
|
+
|
|
237
|
+
<Overture loading={isLoading} exitDuration={150}>
|
|
238
|
+
<h2><Hum className="text-xl font-bold">{user?.name}</Hum></h2>
|
|
239
|
+
<p><Hum className="text-sm text-stone">{user?.email}</Hum></p>
|
|
240
|
+
<Button><Hum>Edit Profile</Hum></Button>
|
|
241
|
+
</Overture>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
During loading, every Hum renders a shimmer sized to its ghost children. When loading finishes, the shimmers fade out, real content appears, and the Gigbag ratchet prevents any height collapse.
|
|
245
|
+
|
|
246
|
+
#### Overture props
|
|
247
|
+
|
|
248
|
+
| Prop | Type | Default | Description |
|
|
249
|
+
|------|------|---------|-------------|
|
|
250
|
+
| `loading` | `boolean` | | Show shimmer (true) or content (false) |
|
|
251
|
+
| `exitDuration` | `number` | | Exit animation duration in ms (match `--concertina-close-duration`) |
|
|
252
|
+
| `as` | `ElementType` | `"div"` | HTML element to render |
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
203
256
|
## Ensemble: temporal stability for collections
|
|
204
257
|
|
|
205
258
|
A list loads from an API. You want shimmer rows while loading, then a smooth transition to real items, and the container must never collapse during the swap.
|
|
@@ -382,6 +435,8 @@ Scrolls an element to the top of its nearest scrollable ancestor. Only touches `
|
|
|
382
435
|
|---------|------|
|
|
383
436
|
| Two variants swap in one slot | Bellows + Slot |
|
|
384
437
|
| Line of text loading from API | Hum |
|
|
438
|
+
| Many Hum instances share one loading state | Vamp + Hum |
|
|
439
|
+
| Card/table/page loading from API | Overture + Hum |
|
|
385
440
|
| List loading from API | Ensemble |
|
|
386
441
|
| Spinner replaced by loaded content | Gigbag + Warmup |
|
|
387
442
|
| Accordion/table shimmer rows | Stub data + WarmupLine (wrapper-once pattern) |
|
package/dist/accordion.cjs
CHANGED
|
@@ -1229,12 +1229,13 @@ var styles_default = `/* concertina \u2014 Radix Accordion expand/collapse with
|
|
|
1229
1229
|
}
|
|
1230
1230
|
|
|
1231
1231
|
/* Inactive Slot hiding \u2014 belt and suspenders.
|
|
1232
|
-
|
|
1233
|
-
|
|
1232
|
+
Primary: inline style on the Slot element (visibility: hidden + opacity: 0).
|
|
1233
|
+
Inline styles can't be overridden by any CSS cascade \u2014 this is the
|
|
1234
|
+
bulletproof layer.
|
|
1235
|
+
Backup: CSS rules below catch edge cases (e.g. if inline styles are
|
|
1236
|
+
stripped by a framework or test harness).
|
|
1234
1237
|
transition: none on descendants prevents children with transition-all
|
|
1235
|
-
from animating the inherited visibility change
|
|
1236
|
-
inherited; transition-all transitions it over the child's duration
|
|
1237
|
-
instead of hiding instantly). */
|
|
1238
|
+
from animating the inherited visibility change. */
|
|
1238
1239
|
.concertina-stable-slot > [inert] {
|
|
1239
1240
|
visibility: hidden;
|
|
1240
1241
|
opacity: 0;
|
package/dist/accordion.js
CHANGED
|
@@ -1190,12 +1190,13 @@ var styles_default = `/* concertina \u2014 Radix Accordion expand/collapse with
|
|
|
1190
1190
|
}
|
|
1191
1191
|
|
|
1192
1192
|
/* Inactive Slot hiding \u2014 belt and suspenders.
|
|
1193
|
-
|
|
1194
|
-
|
|
1193
|
+
Primary: inline style on the Slot element (visibility: hidden + opacity: 0).
|
|
1194
|
+
Inline styles can't be overridden by any CSS cascade \u2014 this is the
|
|
1195
|
+
bulletproof layer.
|
|
1196
|
+
Backup: CSS rules below catch edge cases (e.g. if inline styles are
|
|
1197
|
+
stripped by a framework or test harness).
|
|
1195
1198
|
transition: none on descendants prevents children with transition-all
|
|
1196
|
-
from animating the inherited visibility change
|
|
1197
|
-
inherited; transition-all transitions it over the child's duration
|
|
1198
|
-
instead of hiding instantly). */
|
|
1199
|
+
from animating the inherited visibility change. */
|
|
1199
1200
|
.concertina-stable-slot > [inert] {
|
|
1200
1201
|
visibility: hidden;
|
|
1201
1202
|
opacity: 0;
|
package/dist/index.cjs
CHANGED
|
@@ -40,12 +40,15 @@ __export(index_exports, {
|
|
|
40
40
|
Header: () => Header,
|
|
41
41
|
Hum: () => Hum,
|
|
42
42
|
Item: () => Item2,
|
|
43
|
+
Overture: () => Overture,
|
|
43
44
|
Root: () => Root3,
|
|
44
45
|
Slot: () => Slot,
|
|
45
46
|
StableCollection: () => Ensemble,
|
|
46
47
|
StableSlot: () => Bellows,
|
|
47
48
|
StableText: () => Hum,
|
|
48
49
|
Trigger: () => Trigger2,
|
|
50
|
+
Vamp: () => Vamp,
|
|
51
|
+
VampContext: () => VampContext,
|
|
49
52
|
Warmup: () => Warmup,
|
|
50
53
|
WarmupLine: () => WarmupLine,
|
|
51
54
|
pinToScrollTop: () => pinToScrollTop,
|
|
@@ -56,6 +59,7 @@ __export(index_exports, {
|
|
|
56
59
|
useSize: () => useSize,
|
|
57
60
|
useStableSlot: () => useStableSlot,
|
|
58
61
|
useTransitionLock: () => useTransitionLock,
|
|
62
|
+
useVamp: () => useVamp,
|
|
59
63
|
useWarmupExit: () => useWarmupExit
|
|
60
64
|
});
|
|
61
65
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -1247,12 +1251,13 @@ var styles_default = `/* concertina \u2014 Radix Accordion expand/collapse with
|
|
|
1247
1251
|
}
|
|
1248
1252
|
|
|
1249
1253
|
/* Inactive Slot hiding \u2014 belt and suspenders.
|
|
1250
|
-
|
|
1251
|
-
|
|
1254
|
+
Primary: inline style on the Slot element (visibility: hidden + opacity: 0).
|
|
1255
|
+
Inline styles can't be overridden by any CSS cascade \u2014 this is the
|
|
1256
|
+
bulletproof layer.
|
|
1257
|
+
Backup: CSS rules below catch edge cases (e.g. if inline styles are
|
|
1258
|
+
stripped by a framework or test harness).
|
|
1252
1259
|
transition: none on descendants prevents children with transition-all
|
|
1253
|
-
from animating the inherited visibility change
|
|
1254
|
-
inherited; transition-all transitions it over the child's duration
|
|
1255
|
-
instead of hiding instantly). */
|
|
1260
|
+
from animating the inherited visibility change. */
|
|
1256
1261
|
.concertina-stable-slot > [inert] {
|
|
1257
1262
|
visibility: hidden;
|
|
1258
1263
|
opacity: 0;
|
|
@@ -1412,18 +1417,20 @@ var Bellows = (0, import_react11.forwardRef)(
|
|
|
1412
1417
|
// src/components/slot.tsx
|
|
1413
1418
|
var import_react12 = require("react");
|
|
1414
1419
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1420
|
+
var HIDDEN_STYLE = { visibility: "hidden", opacity: 0 };
|
|
1415
1421
|
var Slot = (0, import_react12.forwardRef)(
|
|
1416
1422
|
function Slot2({ active, note, as: Tag = "div", style, children, ...props }, ref) {
|
|
1417
1423
|
(0, import_react12.useInsertionEffect)(injectStyles, []);
|
|
1418
1424
|
(0, import_react12.useContext)(AxisContext);
|
|
1419
1425
|
const activeNote = (0, import_react12.useContext)(ActiveNoteContext);
|
|
1420
|
-
const isActive = active ?? (note != null
|
|
1426
|
+
const isActive = active ?? (note != null ? note === activeNote : true);
|
|
1427
|
+
const merged = isActive ? style : style ? { ...style, ...HIDDEN_STYLE } : HIDDEN_STYLE;
|
|
1421
1428
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1422
1429
|
Tag,
|
|
1423
1430
|
{
|
|
1424
1431
|
ref,
|
|
1425
1432
|
inert: !isActive || void 0,
|
|
1426
|
-
style,
|
|
1433
|
+
style: merged,
|
|
1427
1434
|
...props,
|
|
1428
1435
|
children
|
|
1429
1436
|
}
|
|
@@ -1432,34 +1439,49 @@ var Slot = (0, import_react12.forwardRef)(
|
|
|
1432
1439
|
);
|
|
1433
1440
|
|
|
1434
1441
|
// src/components/hum.tsx
|
|
1442
|
+
var import_react14 = require("react");
|
|
1443
|
+
|
|
1444
|
+
// src/components/vamp.tsx
|
|
1435
1445
|
var import_react13 = require("react");
|
|
1436
1446
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1437
|
-
var
|
|
1447
|
+
var VampContext = (0, import_react13.createContext)(false);
|
|
1448
|
+
function useVamp() {
|
|
1449
|
+
return (0, import_react13.useContext)(VampContext);
|
|
1450
|
+
}
|
|
1451
|
+
function Vamp({ loading, children }) {
|
|
1452
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(VampContext.Provider, { value: loading, children });
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// src/components/hum.tsx
|
|
1456
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1457
|
+
var Hum = (0, import_react14.forwardRef)(
|
|
1438
1458
|
function Hum2({ loading, as: Tag = "span", className, children, ...props }, ref) {
|
|
1439
|
-
(0,
|
|
1440
|
-
|
|
1459
|
+
(0, import_react14.useInsertionEffect)(injectStyles, []);
|
|
1460
|
+
const vampLoading = useVamp();
|
|
1461
|
+
const isLoading = loading ?? vampLoading;
|
|
1462
|
+
if (isLoading) {
|
|
1441
1463
|
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
1442
|
-
return /* @__PURE__ */ (0,
|
|
1464
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { ref, className: merged, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { inert: true, children }) });
|
|
1443
1465
|
}
|
|
1444
|
-
return /* @__PURE__ */ (0,
|
|
1466
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { ref, className, ...props, children });
|
|
1445
1467
|
}
|
|
1446
1468
|
);
|
|
1447
1469
|
|
|
1448
|
-
// src/components/
|
|
1470
|
+
// src/components/overture.tsx
|
|
1449
1471
|
var import_react18 = require("react");
|
|
1450
1472
|
|
|
1451
1473
|
// src/components/gigbag.tsx
|
|
1452
|
-
var
|
|
1474
|
+
var import_react16 = require("react");
|
|
1453
1475
|
|
|
1454
1476
|
// src/primitives/use-stable-slot.ts
|
|
1455
|
-
var
|
|
1477
|
+
var import_react15 = require("react");
|
|
1456
1478
|
var RATCHET_FLOOR = -Infinity;
|
|
1457
1479
|
function useStableSlot(options = {}) {
|
|
1458
1480
|
const { axis = "both" } = options;
|
|
1459
|
-
const [style, setStyle] = (0,
|
|
1460
|
-
const maxRef = (0,
|
|
1461
|
-
const observerRef = (0,
|
|
1462
|
-
const ref = (0,
|
|
1481
|
+
const [style, setStyle] = (0, import_react15.useState)({});
|
|
1482
|
+
const maxRef = (0, import_react15.useRef)({ w: RATCHET_FLOOR, h: RATCHET_FLOOR });
|
|
1483
|
+
const observerRef = (0, import_react15.useRef)(null);
|
|
1484
|
+
const ref = (0, import_react15.useCallback)(
|
|
1463
1485
|
(el) => {
|
|
1464
1486
|
if (observerRef.current) {
|
|
1465
1487
|
observerRef.current.disconnect();
|
|
@@ -1506,13 +1528,13 @@ function useStableSlot(options = {}) {
|
|
|
1506
1528
|
}
|
|
1507
1529
|
|
|
1508
1530
|
// src/components/gigbag.tsx
|
|
1509
|
-
var
|
|
1510
|
-
var Gigbag = (0,
|
|
1531
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1532
|
+
var Gigbag = (0, import_react16.forwardRef)(
|
|
1511
1533
|
function Gigbag2({ axis = "height", as: Tag = "div", className, style, children, ...props }, fwdRef) {
|
|
1512
|
-
(0,
|
|
1534
|
+
(0, import_react16.useInsertionEffect)(injectStyles, []);
|
|
1513
1535
|
const { ref: ratchetRef, style: ratchetStyle } = useStableSlot({ axis });
|
|
1514
1536
|
const merged = className ? `concertina-gigbag ${className}` : "concertina-gigbag";
|
|
1515
|
-
return /* @__PURE__ */ (0,
|
|
1537
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1516
1538
|
Tag,
|
|
1517
1539
|
{
|
|
1518
1540
|
ref: mergeRefs(ratchetRef, fwdRef),
|
|
@@ -1525,32 +1547,6 @@ var Gigbag = (0, import_react15.forwardRef)(
|
|
|
1525
1547
|
}
|
|
1526
1548
|
);
|
|
1527
1549
|
|
|
1528
|
-
// src/components/warmup.tsx
|
|
1529
|
-
var import_react16 = require("react");
|
|
1530
|
-
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1531
|
-
var Warmup = (0, import_react16.forwardRef)(
|
|
1532
|
-
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
1533
|
-
(0, import_react16.useInsertionEffect)(injectStyles, []);
|
|
1534
|
-
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
1535
|
-
const count2 = columns ? rows * columns : rows;
|
|
1536
|
-
const cells = Array.from({ length: count2 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "concertina-warmup-bone", children: [
|
|
1537
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "concertina-warmup-line" }),
|
|
1538
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "concertina-warmup-line" })
|
|
1539
|
-
] }, i));
|
|
1540
|
-
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
1541
|
-
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1542
|
-
Tag,
|
|
1543
|
-
{
|
|
1544
|
-
ref,
|
|
1545
|
-
className: merged,
|
|
1546
|
-
style: gridStyle,
|
|
1547
|
-
...props,
|
|
1548
|
-
children: cells
|
|
1549
|
-
}
|
|
1550
|
-
);
|
|
1551
|
-
}
|
|
1552
|
-
);
|
|
1553
|
-
|
|
1554
1550
|
// src/primitives/use-warmup-exit.ts
|
|
1555
1551
|
var import_react17 = require("react");
|
|
1556
1552
|
function useWarmupExit(loading, duration) {
|
|
@@ -1573,8 +1569,48 @@ function useWarmupExit(loading, duration) {
|
|
|
1573
1569
|
};
|
|
1574
1570
|
}
|
|
1575
1571
|
|
|
1576
|
-
// src/components/
|
|
1572
|
+
// src/components/overture.tsx
|
|
1577
1573
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1574
|
+
var Overture = (0, import_react18.forwardRef)(
|
|
1575
|
+
function Overture2({ loading, exitDuration, as: Tag = "div", className, children, ...props }, ref) {
|
|
1576
|
+
(0, import_react18.useInsertionEffect)(injectStyles, []);
|
|
1577
|
+
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
1578
|
+
const merged = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
1579
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Gigbag, { ref, axis: "height", as: Tag, className: merged, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Vamp, { loading: showWarmup, children }) });
|
|
1580
|
+
}
|
|
1581
|
+
);
|
|
1582
|
+
|
|
1583
|
+
// src/components/ensemble.tsx
|
|
1584
|
+
var import_react20 = require("react");
|
|
1585
|
+
|
|
1586
|
+
// src/components/warmup.tsx
|
|
1587
|
+
var import_react19 = require("react");
|
|
1588
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1589
|
+
var Warmup = (0, import_react19.forwardRef)(
|
|
1590
|
+
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
1591
|
+
(0, import_react19.useInsertionEffect)(injectStyles, []);
|
|
1592
|
+
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
1593
|
+
const count2 = columns ? rows * columns : rows;
|
|
1594
|
+
const cells = Array.from({ length: count2 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "concertina-warmup-bone", children: [
|
|
1595
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "concertina-warmup-line" }),
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "concertina-warmup-line" })
|
|
1597
|
+
] }, i));
|
|
1598
|
+
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
1599
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1600
|
+
Tag,
|
|
1601
|
+
{
|
|
1602
|
+
ref,
|
|
1603
|
+
className: merged,
|
|
1604
|
+
style: gridStyle,
|
|
1605
|
+
...props,
|
|
1606
|
+
children: cells
|
|
1607
|
+
}
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
// src/components/ensemble.tsx
|
|
1613
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1578
1614
|
function EnsembleInner({
|
|
1579
1615
|
items,
|
|
1580
1616
|
loading,
|
|
@@ -1585,33 +1621,33 @@ function EnsembleInner({
|
|
|
1585
1621
|
className,
|
|
1586
1622
|
...props
|
|
1587
1623
|
}, ref) {
|
|
1588
|
-
(0,
|
|
1624
|
+
(0, import_react20.useInsertionEffect)(injectStyles, []);
|
|
1589
1625
|
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
1590
1626
|
const warmupClass = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
1591
|
-
return /* @__PURE__ */ (0,
|
|
1627
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Gigbag, { ref, axis: "height", as: Tag, ...props, children: showWarmup ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Warmup, { rows: stubCount, className: warmupClass }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Tag, { className, children: items.map(renderItem) }) });
|
|
1592
1628
|
}
|
|
1593
|
-
var Ensemble = (0,
|
|
1629
|
+
var Ensemble = (0, import_react20.forwardRef)(EnsembleInner);
|
|
1594
1630
|
|
|
1595
1631
|
// src/components/warmup-line.tsx
|
|
1596
|
-
var
|
|
1597
|
-
var
|
|
1598
|
-
var WarmupLine = (0,
|
|
1632
|
+
var import_react21 = require("react");
|
|
1633
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1634
|
+
var WarmupLine = (0, import_react21.forwardRef)(
|
|
1599
1635
|
function WarmupLine2({ as: Tag = "div", className, ...props }, ref) {
|
|
1600
|
-
(0,
|
|
1636
|
+
(0, import_react21.useInsertionEffect)(injectStyles, []);
|
|
1601
1637
|
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
1602
|
-
return /* @__PURE__ */ (0,
|
|
1638
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Tag, { ref, className: merged, ...props });
|
|
1603
1639
|
}
|
|
1604
1640
|
);
|
|
1605
1641
|
|
|
1606
1642
|
// src/components/glide.tsx
|
|
1607
|
-
var
|
|
1643
|
+
var import_react23 = require("react");
|
|
1608
1644
|
|
|
1609
1645
|
// src/primitives/use-presence.ts
|
|
1610
|
-
var
|
|
1646
|
+
var import_react22 = require("react");
|
|
1611
1647
|
function usePresence2(show) {
|
|
1612
|
-
const [mounted, setMounted] = (0,
|
|
1613
|
-
const [phase, setPhase] = (0,
|
|
1614
|
-
(0,
|
|
1648
|
+
const [mounted, setMounted] = (0, import_react22.useState)(show);
|
|
1649
|
+
const [phase, setPhase] = (0, import_react22.useState)(show ? "entered" : "exiting");
|
|
1650
|
+
(0, import_react22.useEffect)(() => {
|
|
1615
1651
|
if (show) {
|
|
1616
1652
|
setMounted(true);
|
|
1617
1653
|
setPhase("entering");
|
|
@@ -1619,7 +1655,7 @@ function usePresence2(show) {
|
|
|
1619
1655
|
setPhase("exiting");
|
|
1620
1656
|
}
|
|
1621
1657
|
}, [show]);
|
|
1622
|
-
const onAnimationEnd = (0,
|
|
1658
|
+
const onAnimationEnd = (0, import_react22.useCallback)(
|
|
1623
1659
|
(e) => {
|
|
1624
1660
|
if (e.target !== e.currentTarget) return;
|
|
1625
1661
|
if (phase === "entering") setPhase("entered");
|
|
@@ -1631,15 +1667,15 @@ function usePresence2(show) {
|
|
|
1631
1667
|
}
|
|
1632
1668
|
|
|
1633
1669
|
// src/components/glide.tsx
|
|
1634
|
-
var
|
|
1635
|
-
var Glide = (0,
|
|
1670
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1671
|
+
var Glide = (0, import_react23.forwardRef)(
|
|
1636
1672
|
function Glide2({ show, as: Tag = "div", className, children, ...props }, ref) {
|
|
1637
|
-
(0,
|
|
1673
|
+
(0, import_react23.useInsertionEffect)(injectStyles, []);
|
|
1638
1674
|
const { mounted, phase, onAnimationEnd } = usePresence2(show);
|
|
1639
1675
|
if (!mounted) return null;
|
|
1640
1676
|
const phaseClass = phase === "entering" ? "concertina-glide-entering" : phase === "exiting" ? "concertina-glide-exiting" : "";
|
|
1641
1677
|
const merged = ["concertina-glide", phaseClass, className].filter(Boolean).join(" ");
|
|
1642
|
-
return /* @__PURE__ */ (0,
|
|
1678
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1643
1679
|
Tag,
|
|
1644
1680
|
{
|
|
1645
1681
|
ref,
|
|
@@ -1653,12 +1689,12 @@ var Glide = (0, import_react21.forwardRef)(
|
|
|
1653
1689
|
);
|
|
1654
1690
|
|
|
1655
1691
|
// src/primitives/use-size.ts
|
|
1656
|
-
var
|
|
1692
|
+
var import_react24 = require("react");
|
|
1657
1693
|
var NO_OBSERVATION = { width: Number.NaN, height: Number.NaN };
|
|
1658
1694
|
function useSize() {
|
|
1659
|
-
const [size, setSize] = (0,
|
|
1660
|
-
const observerRef = (0,
|
|
1661
|
-
const ref = (0,
|
|
1695
|
+
const [size, setSize] = (0, import_react24.useState)(NO_OBSERVATION);
|
|
1696
|
+
const observerRef = (0, import_react24.useRef)(null);
|
|
1697
|
+
const ref = (0, import_react24.useCallback)((el) => {
|
|
1662
1698
|
if (observerRef.current) {
|
|
1663
1699
|
observerRef.current.disconnect();
|
|
1664
1700
|
observerRef.current = null;
|
|
@@ -1687,12 +1723,12 @@ function useSize() {
|
|
|
1687
1723
|
}
|
|
1688
1724
|
|
|
1689
1725
|
// src/accordion/use-concertina.ts
|
|
1690
|
-
var
|
|
1726
|
+
var import_react25 = require("react");
|
|
1691
1727
|
function useConcertina() {
|
|
1692
|
-
const [value, setValue] = (0,
|
|
1693
|
-
const [switching, setSwitching] = (0,
|
|
1694
|
-
const itemRefs = (0,
|
|
1695
|
-
const onValueChange = (0,
|
|
1728
|
+
const [value, setValue] = (0, import_react25.useState)("");
|
|
1729
|
+
const [switching, setSwitching] = (0, import_react25.useState)(false);
|
|
1730
|
+
const itemRefs = (0, import_react25.useRef)({});
|
|
1731
|
+
const onValueChange = (0, import_react25.useCallback)(
|
|
1696
1732
|
(newValue) => {
|
|
1697
1733
|
if (!newValue) {
|
|
1698
1734
|
setSwitching(false);
|
|
@@ -1704,14 +1740,14 @@ function useConcertina() {
|
|
|
1704
1740
|
},
|
|
1705
1741
|
[value]
|
|
1706
1742
|
);
|
|
1707
|
-
(0,
|
|
1743
|
+
(0, import_react25.useLayoutEffect)(() => {
|
|
1708
1744
|
if (!value) return;
|
|
1709
1745
|
pinToScrollTop(itemRefs.current[value]);
|
|
1710
1746
|
}, [value]);
|
|
1711
|
-
(0,
|
|
1747
|
+
(0, import_react25.useEffect)(() => {
|
|
1712
1748
|
if (switching) setSwitching(false);
|
|
1713
1749
|
}, [switching]);
|
|
1714
|
-
const getItemRef = (0,
|
|
1750
|
+
const getItemRef = (0, import_react25.useCallback)(
|
|
1715
1751
|
(id) => (el) => {
|
|
1716
1752
|
itemRefs.current[id] = el;
|
|
1717
1753
|
},
|
|
@@ -1736,12 +1772,15 @@ function useConcertina() {
|
|
|
1736
1772
|
Header,
|
|
1737
1773
|
Hum,
|
|
1738
1774
|
Item,
|
|
1775
|
+
Overture,
|
|
1739
1776
|
Root,
|
|
1740
1777
|
Slot,
|
|
1741
1778
|
StableCollection,
|
|
1742
1779
|
StableSlot,
|
|
1743
1780
|
StableText,
|
|
1744
1781
|
Trigger,
|
|
1782
|
+
Vamp,
|
|
1783
|
+
VampContext,
|
|
1745
1784
|
Warmup,
|
|
1746
1785
|
WarmupLine,
|
|
1747
1786
|
pinToScrollTop,
|
|
@@ -1752,5 +1791,6 @@ function useConcertina() {
|
|
|
1752
1791
|
useSize,
|
|
1753
1792
|
useStableSlot,
|
|
1754
1793
|
useTransitionLock,
|
|
1794
|
+
useVamp,
|
|
1755
1795
|
useWarmupExit
|
|
1756
1796
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -2,6 +2,7 @@ export { ConcertinaContext, ConcertinaRootProps, ConcertinaStore, Content, Item,
|
|
|
2
2
|
export { Header, Trigger } from '@radix-ui/react-accordion';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { HTMLAttributes, ElementType, ReactNode, Ref, ReactElement, AnimationEvent, DependencyList, CSSProperties } from 'react';
|
|
5
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
6
|
|
|
6
7
|
type Axis = "width" | "height" | "both";
|
|
7
8
|
interface BellowsProps extends HTMLAttributes<HTMLElement> {
|
|
@@ -34,15 +35,18 @@ interface SlotProps extends HTMLAttributes<HTMLElement> {
|
|
|
34
35
|
* All slots overlap via CSS grid. Inactive slots are hidden
|
|
35
36
|
* but still contribute to grid cell sizing.
|
|
36
37
|
*
|
|
37
|
-
* Inactive hiding
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* Inactive hiding uses inline styles (can't be overridden by CSS cascade)
|
|
39
|
+
* plus the [inert] attribute for accessibility (non-focusable, non-interactive).
|
|
40
|
+
* CSS `.concertina-stable-slot > [inert]` serves as a backup.
|
|
40
41
|
*/
|
|
41
42
|
declare const Slot: react.ForwardRefExoticComponent<SlotProps & react.RefAttributes<HTMLElement>>;
|
|
42
43
|
|
|
43
44
|
interface HumProps extends HTMLAttributes<HTMLElement> {
|
|
44
|
-
/**
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Whether data is loading. Shows shimmer when true, children when false.
|
|
47
|
+
* When omitted, falls back to the nearest `<Vamp>` ancestor's loading state.
|
|
48
|
+
*/
|
|
49
|
+
loading?: boolean;
|
|
46
50
|
/** HTML element to render. Default: "span". */
|
|
47
51
|
as?: ElementType;
|
|
48
52
|
}
|
|
@@ -56,9 +60,63 @@ interface HumProps extends HTMLAttributes<HTMLElement> {
|
|
|
56
60
|
*
|
|
57
61
|
* The className is passed through so `1lh` inherits the correct font
|
|
58
62
|
* metrics from the consuming context.
|
|
63
|
+
*
|
|
64
|
+
* When no explicit `loading` prop is provided, Hum reads from the
|
|
65
|
+
* nearest `<Vamp>` ancestor. This lets a single provider control
|
|
66
|
+
* shimmer state for an entire subtree.
|
|
59
67
|
*/
|
|
60
68
|
declare const Hum: react.ForwardRefExoticComponent<HumProps & react.RefAttributes<HTMLElement>>;
|
|
61
69
|
|
|
70
|
+
/** Context carrying the ambient loading state set by Vamp. */
|
|
71
|
+
declare const VampContext: react.Context<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Read the nearest Vamp's loading state.
|
|
74
|
+
* Returns `false` when no Vamp ancestor exists.
|
|
75
|
+
*/
|
|
76
|
+
declare function useVamp(): boolean;
|
|
77
|
+
interface VampProps {
|
|
78
|
+
/** Whether the subtree is in a loading/warmup state. */
|
|
79
|
+
loading: boolean;
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Ambient loading provider — musical "vamping" (repeating a pattern
|
|
84
|
+
* while waiting for a cue).
|
|
85
|
+
*
|
|
86
|
+
* Wrapping a subtree in `<Vamp loading>` lets every nested `<Hum>`
|
|
87
|
+
* pick up the loading state automatically, without threading an
|
|
88
|
+
* explicit `loading` prop through every cell.
|
|
89
|
+
*/
|
|
90
|
+
declare function Vamp({ loading, children }: VampProps): react_jsx_runtime.JSX.Element;
|
|
91
|
+
|
|
92
|
+
interface OvertureProps extends HTMLAttributes<HTMLElement> {
|
|
93
|
+
/** Whether data is loading. Sets Vamp context for all nested Hum instances. */
|
|
94
|
+
loading: boolean;
|
|
95
|
+
/** Exit animation duration in ms. Must match CSS --concertina-close-duration. */
|
|
96
|
+
exitDuration: number;
|
|
97
|
+
/** HTML element to render. Default: "div". */
|
|
98
|
+
as?: ElementType;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Loading-aware subtree wrapper — the opening act before the real content.
|
|
102
|
+
*
|
|
103
|
+
* Composes three behaviors into one component:
|
|
104
|
+
* - **Vamp** context: every nested `<Hum>` reads loading state automatically.
|
|
105
|
+
* - **Gigbag** ratchet: container never shrinks during the shimmer-to-content swap.
|
|
106
|
+
* - **Exit transition**: applies `concertina-warmup-exiting` class during the
|
|
107
|
+
* fade-out so shimmer lines animate before real content mounts.
|
|
108
|
+
*
|
|
109
|
+
* Write one JSX tree for both states. Hum instances handle the visual toggle.
|
|
110
|
+
*
|
|
111
|
+
* ```tsx
|
|
112
|
+
* <Overture loading={isLoading} exitDuration={150}>
|
|
113
|
+
* <h2><Hum className="text-xl">{user?.name}</Hum></h2>
|
|
114
|
+
* <p><Hum className="text-sm">{user?.bio}</Hum></p>
|
|
115
|
+
* </Overture>
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare const Overture: react.ForwardRefExoticComponent<OvertureProps & react.RefAttributes<HTMLElement>>;
|
|
119
|
+
|
|
62
120
|
interface EnsembleProps<T> extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
63
121
|
/** Data items to render. */
|
|
64
122
|
items: T[];
|
|
@@ -286,4 +344,4 @@ declare function useWarmupExit(loading: boolean, duration: number): {
|
|
|
286
344
|
exiting: boolean;
|
|
287
345
|
};
|
|
288
346
|
|
|
289
|
-
export { type Axis, Bellows, type BellowsProps, Ensemble, type EnsembleProps, Gigbag, type GigbagProps, Glide, type GlideProps, Hum, type HumProps, type Phase, type Size, Slot, type SlotProps, Ensemble as StableCollection, type EnsembleProps as StableCollectionProps, Bellows as StableSlot, type BellowsProps as StableSlotProps, Hum as StableText, type HumProps as StableTextProps, type UsePresenceReturn, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useWarmupExit };
|
|
347
|
+
export { type Axis, Bellows, type BellowsProps, Ensemble, type EnsembleProps, Gigbag, type GigbagProps, Glide, type GlideProps, Hum, type HumProps, Overture, type OvertureProps, type Phase, type Size, Slot, type SlotProps, Ensemble as StableCollection, type EnsembleProps as StableCollectionProps, Bellows as StableSlot, type BellowsProps as StableSlotProps, Hum as StableText, type HumProps as StableTextProps, type UsePresenceReturn, Vamp, VampContext, type VampProps, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useVamp, useWarmupExit };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { ConcertinaContext, ConcertinaRootProps, ConcertinaStore, Content, Item,
|
|
|
2
2
|
export { Header, Trigger } from '@radix-ui/react-accordion';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { HTMLAttributes, ElementType, ReactNode, Ref, ReactElement, AnimationEvent, DependencyList, CSSProperties } from 'react';
|
|
5
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
6
|
|
|
6
7
|
type Axis = "width" | "height" | "both";
|
|
7
8
|
interface BellowsProps extends HTMLAttributes<HTMLElement> {
|
|
@@ -34,15 +35,18 @@ interface SlotProps extends HTMLAttributes<HTMLElement> {
|
|
|
34
35
|
* All slots overlap via CSS grid. Inactive slots are hidden
|
|
35
36
|
* but still contribute to grid cell sizing.
|
|
36
37
|
*
|
|
37
|
-
* Inactive hiding
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* Inactive hiding uses inline styles (can't be overridden by CSS cascade)
|
|
39
|
+
* plus the [inert] attribute for accessibility (non-focusable, non-interactive).
|
|
40
|
+
* CSS `.concertina-stable-slot > [inert]` serves as a backup.
|
|
40
41
|
*/
|
|
41
42
|
declare const Slot: react.ForwardRefExoticComponent<SlotProps & react.RefAttributes<HTMLElement>>;
|
|
42
43
|
|
|
43
44
|
interface HumProps extends HTMLAttributes<HTMLElement> {
|
|
44
|
-
/**
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Whether data is loading. Shows shimmer when true, children when false.
|
|
47
|
+
* When omitted, falls back to the nearest `<Vamp>` ancestor's loading state.
|
|
48
|
+
*/
|
|
49
|
+
loading?: boolean;
|
|
46
50
|
/** HTML element to render. Default: "span". */
|
|
47
51
|
as?: ElementType;
|
|
48
52
|
}
|
|
@@ -56,9 +60,63 @@ interface HumProps extends HTMLAttributes<HTMLElement> {
|
|
|
56
60
|
*
|
|
57
61
|
* The className is passed through so `1lh` inherits the correct font
|
|
58
62
|
* metrics from the consuming context.
|
|
63
|
+
*
|
|
64
|
+
* When no explicit `loading` prop is provided, Hum reads from the
|
|
65
|
+
* nearest `<Vamp>` ancestor. This lets a single provider control
|
|
66
|
+
* shimmer state for an entire subtree.
|
|
59
67
|
*/
|
|
60
68
|
declare const Hum: react.ForwardRefExoticComponent<HumProps & react.RefAttributes<HTMLElement>>;
|
|
61
69
|
|
|
70
|
+
/** Context carrying the ambient loading state set by Vamp. */
|
|
71
|
+
declare const VampContext: react.Context<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Read the nearest Vamp's loading state.
|
|
74
|
+
* Returns `false` when no Vamp ancestor exists.
|
|
75
|
+
*/
|
|
76
|
+
declare function useVamp(): boolean;
|
|
77
|
+
interface VampProps {
|
|
78
|
+
/** Whether the subtree is in a loading/warmup state. */
|
|
79
|
+
loading: boolean;
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Ambient loading provider — musical "vamping" (repeating a pattern
|
|
84
|
+
* while waiting for a cue).
|
|
85
|
+
*
|
|
86
|
+
* Wrapping a subtree in `<Vamp loading>` lets every nested `<Hum>`
|
|
87
|
+
* pick up the loading state automatically, without threading an
|
|
88
|
+
* explicit `loading` prop through every cell.
|
|
89
|
+
*/
|
|
90
|
+
declare function Vamp({ loading, children }: VampProps): react_jsx_runtime.JSX.Element;
|
|
91
|
+
|
|
92
|
+
interface OvertureProps extends HTMLAttributes<HTMLElement> {
|
|
93
|
+
/** Whether data is loading. Sets Vamp context for all nested Hum instances. */
|
|
94
|
+
loading: boolean;
|
|
95
|
+
/** Exit animation duration in ms. Must match CSS --concertina-close-duration. */
|
|
96
|
+
exitDuration: number;
|
|
97
|
+
/** HTML element to render. Default: "div". */
|
|
98
|
+
as?: ElementType;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Loading-aware subtree wrapper — the opening act before the real content.
|
|
102
|
+
*
|
|
103
|
+
* Composes three behaviors into one component:
|
|
104
|
+
* - **Vamp** context: every nested `<Hum>` reads loading state automatically.
|
|
105
|
+
* - **Gigbag** ratchet: container never shrinks during the shimmer-to-content swap.
|
|
106
|
+
* - **Exit transition**: applies `concertina-warmup-exiting` class during the
|
|
107
|
+
* fade-out so shimmer lines animate before real content mounts.
|
|
108
|
+
*
|
|
109
|
+
* Write one JSX tree for both states. Hum instances handle the visual toggle.
|
|
110
|
+
*
|
|
111
|
+
* ```tsx
|
|
112
|
+
* <Overture loading={isLoading} exitDuration={150}>
|
|
113
|
+
* <h2><Hum className="text-xl">{user?.name}</Hum></h2>
|
|
114
|
+
* <p><Hum className="text-sm">{user?.bio}</Hum></p>
|
|
115
|
+
* </Overture>
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare const Overture: react.ForwardRefExoticComponent<OvertureProps & react.RefAttributes<HTMLElement>>;
|
|
119
|
+
|
|
62
120
|
interface EnsembleProps<T> extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
63
121
|
/** Data items to render. */
|
|
64
122
|
items: T[];
|
|
@@ -286,4 +344,4 @@ declare function useWarmupExit(loading: boolean, duration: number): {
|
|
|
286
344
|
exiting: boolean;
|
|
287
345
|
};
|
|
288
346
|
|
|
289
|
-
export { type Axis, Bellows, type BellowsProps, Ensemble, type EnsembleProps, Gigbag, type GigbagProps, Glide, type GlideProps, Hum, type HumProps, type Phase, type Size, Slot, type SlotProps, Ensemble as StableCollection, type EnsembleProps as StableCollectionProps, Bellows as StableSlot, type BellowsProps as StableSlotProps, Hum as StableText, type HumProps as StableTextProps, type UsePresenceReturn, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useWarmupExit };
|
|
347
|
+
export { type Axis, Bellows, type BellowsProps, Ensemble, type EnsembleProps, Gigbag, type GigbagProps, Glide, type GlideProps, Hum, type HumProps, Overture, type OvertureProps, type Phase, type Size, Slot, type SlotProps, Ensemble as StableCollection, type EnsembleProps as StableCollectionProps, Bellows as StableSlot, type BellowsProps as StableSlotProps, Hum as StableText, type HumProps as StableTextProps, type UsePresenceReturn, Vamp, VampContext, type VampProps, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useVamp, useWarmupExit };
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
useExpanded,
|
|
14
14
|
useScrollPin,
|
|
15
15
|
useTransitionLock
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-OGJMPKZX.js";
|
|
17
17
|
|
|
18
18
|
// src/components/bellows.tsx
|
|
19
19
|
import {
|
|
@@ -39,18 +39,20 @@ import {
|
|
|
39
39
|
useInsertionEffect as useInsertionEffect2
|
|
40
40
|
} from "react";
|
|
41
41
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
42
|
+
var HIDDEN_STYLE = { visibility: "hidden", opacity: 0 };
|
|
42
43
|
var Slot = forwardRef2(
|
|
43
44
|
function Slot2({ active, note, as: Tag = "div", style, children, ...props }, ref) {
|
|
44
45
|
useInsertionEffect2(injectStyles, []);
|
|
45
46
|
useContext(AxisContext);
|
|
46
47
|
const activeNote = useContext(ActiveNoteContext);
|
|
47
|
-
const isActive = active ?? (note != null
|
|
48
|
+
const isActive = active ?? (note != null ? note === activeNote : true);
|
|
49
|
+
const merged = isActive ? style : style ? { ...style, ...HIDDEN_STYLE } : HIDDEN_STYLE;
|
|
48
50
|
return /* @__PURE__ */ jsx2(
|
|
49
51
|
Tag,
|
|
50
52
|
{
|
|
51
53
|
ref,
|
|
52
54
|
inert: !isActive || void 0,
|
|
53
|
-
style,
|
|
55
|
+
style: merged,
|
|
54
56
|
...props,
|
|
55
57
|
children
|
|
56
58
|
}
|
|
@@ -63,22 +65,37 @@ import {
|
|
|
63
65
|
forwardRef as forwardRef3,
|
|
64
66
|
useInsertionEffect as useInsertionEffect3
|
|
65
67
|
} from "react";
|
|
68
|
+
|
|
69
|
+
// src/components/vamp.tsx
|
|
70
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
66
71
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
72
|
+
var VampContext = createContext2(false);
|
|
73
|
+
function useVamp() {
|
|
74
|
+
return useContext2(VampContext);
|
|
75
|
+
}
|
|
76
|
+
function Vamp({ loading, children }) {
|
|
77
|
+
return /* @__PURE__ */ jsx3(VampContext.Provider, { value: loading, children });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/components/hum.tsx
|
|
81
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
67
82
|
var Hum = forwardRef3(
|
|
68
83
|
function Hum2({ loading, as: Tag = "span", className, children, ...props }, ref) {
|
|
69
84
|
useInsertionEffect3(injectStyles, []);
|
|
70
|
-
|
|
85
|
+
const vampLoading = useVamp();
|
|
86
|
+
const isLoading = loading ?? vampLoading;
|
|
87
|
+
if (isLoading) {
|
|
71
88
|
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
72
|
-
return /* @__PURE__ */
|
|
89
|
+
return /* @__PURE__ */ jsx4(Tag, { ref, className: merged, ...props, children: /* @__PURE__ */ jsx4(Tag, { inert: true, children }) });
|
|
73
90
|
}
|
|
74
|
-
return /* @__PURE__ */
|
|
91
|
+
return /* @__PURE__ */ jsx4(Tag, { ref, className, ...props, children });
|
|
75
92
|
}
|
|
76
93
|
);
|
|
77
94
|
|
|
78
|
-
// src/components/
|
|
95
|
+
// src/components/overture.tsx
|
|
79
96
|
import {
|
|
80
|
-
forwardRef as
|
|
81
|
-
useInsertionEffect as
|
|
97
|
+
forwardRef as forwardRef5,
|
|
98
|
+
useInsertionEffect as useInsertionEffect5
|
|
82
99
|
} from "react";
|
|
83
100
|
|
|
84
101
|
// src/components/gigbag.tsx
|
|
@@ -139,13 +156,13 @@ function useStableSlot(options = {}) {
|
|
|
139
156
|
}
|
|
140
157
|
|
|
141
158
|
// src/components/gigbag.tsx
|
|
142
|
-
import { jsx as
|
|
159
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
143
160
|
var Gigbag = forwardRef4(
|
|
144
161
|
function Gigbag2({ axis = "height", as: Tag = "div", className, style, children, ...props }, fwdRef) {
|
|
145
162
|
useInsertionEffect4(injectStyles, []);
|
|
146
163
|
const { ref: ratchetRef, style: ratchetStyle } = useStableSlot({ axis });
|
|
147
164
|
const merged = className ? `concertina-gigbag ${className}` : "concertina-gigbag";
|
|
148
|
-
return /* @__PURE__ */
|
|
165
|
+
return /* @__PURE__ */ jsx5(
|
|
149
166
|
Tag,
|
|
150
167
|
{
|
|
151
168
|
ref: mergeRefs(ratchetRef, fwdRef),
|
|
@@ -158,32 +175,6 @@ var Gigbag = forwardRef4(
|
|
|
158
175
|
}
|
|
159
176
|
);
|
|
160
177
|
|
|
161
|
-
// src/components/warmup.tsx
|
|
162
|
-
import { forwardRef as forwardRef5, useInsertionEffect as useInsertionEffect5 } from "react";
|
|
163
|
-
import { jsx as jsx5, jsxs } from "react/jsx-runtime";
|
|
164
|
-
var Warmup = forwardRef5(
|
|
165
|
-
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
166
|
-
useInsertionEffect5(injectStyles, []);
|
|
167
|
-
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
168
|
-
const count = columns ? rows * columns : rows;
|
|
169
|
-
const cells = Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "concertina-warmup-bone", children: [
|
|
170
|
-
/* @__PURE__ */ jsx5("div", { className: "concertina-warmup-line" }),
|
|
171
|
-
/* @__PURE__ */ jsx5("div", { className: "concertina-warmup-line" })
|
|
172
|
-
] }, i));
|
|
173
|
-
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
174
|
-
return /* @__PURE__ */ jsx5(
|
|
175
|
-
Tag,
|
|
176
|
-
{
|
|
177
|
-
ref,
|
|
178
|
-
className: merged,
|
|
179
|
-
style: gridStyle,
|
|
180
|
-
...props,
|
|
181
|
-
children: cells
|
|
182
|
-
}
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
);
|
|
186
|
-
|
|
187
178
|
// src/primitives/use-warmup-exit.ts
|
|
188
179
|
import { useState as useState2, useEffect, useRef as useRef2 } from "react";
|
|
189
180
|
function useWarmupExit(loading, duration) {
|
|
@@ -206,8 +197,51 @@ function useWarmupExit(loading, duration) {
|
|
|
206
197
|
};
|
|
207
198
|
}
|
|
208
199
|
|
|
209
|
-
// src/components/
|
|
200
|
+
// src/components/overture.tsx
|
|
210
201
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
202
|
+
var Overture = forwardRef5(
|
|
203
|
+
function Overture2({ loading, exitDuration, as: Tag = "div", className, children, ...props }, ref) {
|
|
204
|
+
useInsertionEffect5(injectStyles, []);
|
|
205
|
+
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
206
|
+
const merged = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
207
|
+
return /* @__PURE__ */ jsx6(Gigbag, { ref, axis: "height", as: Tag, className: merged, ...props, children: /* @__PURE__ */ jsx6(Vamp, { loading: showWarmup, children }) });
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// src/components/ensemble.tsx
|
|
212
|
+
import {
|
|
213
|
+
forwardRef as forwardRef7,
|
|
214
|
+
useInsertionEffect as useInsertionEffect7
|
|
215
|
+
} from "react";
|
|
216
|
+
|
|
217
|
+
// src/components/warmup.tsx
|
|
218
|
+
import { forwardRef as forwardRef6, useInsertionEffect as useInsertionEffect6 } from "react";
|
|
219
|
+
import { jsx as jsx7, jsxs } from "react/jsx-runtime";
|
|
220
|
+
var Warmup = forwardRef6(
|
|
221
|
+
function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
|
|
222
|
+
useInsertionEffect6(injectStyles, []);
|
|
223
|
+
const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
|
|
224
|
+
const count = columns ? rows * columns : rows;
|
|
225
|
+
const cells = Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "concertina-warmup-bone", children: [
|
|
226
|
+
/* @__PURE__ */ jsx7("div", { className: "concertina-warmup-line" }),
|
|
227
|
+
/* @__PURE__ */ jsx7("div", { className: "concertina-warmup-line" })
|
|
228
|
+
] }, i));
|
|
229
|
+
const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
|
|
230
|
+
return /* @__PURE__ */ jsx7(
|
|
231
|
+
Tag,
|
|
232
|
+
{
|
|
233
|
+
ref,
|
|
234
|
+
className: merged,
|
|
235
|
+
style: gridStyle,
|
|
236
|
+
...props,
|
|
237
|
+
children: cells
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// src/components/ensemble.tsx
|
|
244
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
211
245
|
function EnsembleInner({
|
|
212
246
|
items,
|
|
213
247
|
loading,
|
|
@@ -218,28 +252,28 @@ function EnsembleInner({
|
|
|
218
252
|
className,
|
|
219
253
|
...props
|
|
220
254
|
}, ref) {
|
|
221
|
-
|
|
255
|
+
useInsertionEffect7(injectStyles, []);
|
|
222
256
|
const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
|
|
223
257
|
const warmupClass = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
|
|
224
|
-
return /* @__PURE__ */
|
|
258
|
+
return /* @__PURE__ */ jsx8(Gigbag, { ref, axis: "height", as: Tag, ...props, children: showWarmup ? /* @__PURE__ */ jsx8(Warmup, { rows: stubCount, className: warmupClass }) : /* @__PURE__ */ jsx8(Tag, { className, children: items.map(renderItem) }) });
|
|
225
259
|
}
|
|
226
|
-
var Ensemble =
|
|
260
|
+
var Ensemble = forwardRef7(EnsembleInner);
|
|
227
261
|
|
|
228
262
|
// src/components/warmup-line.tsx
|
|
229
|
-
import { forwardRef as
|
|
230
|
-
import { jsx as
|
|
231
|
-
var WarmupLine =
|
|
263
|
+
import { forwardRef as forwardRef8, useInsertionEffect as useInsertionEffect8 } from "react";
|
|
264
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
265
|
+
var WarmupLine = forwardRef8(
|
|
232
266
|
function WarmupLine2({ as: Tag = "div", className, ...props }, ref) {
|
|
233
|
-
|
|
267
|
+
useInsertionEffect8(injectStyles, []);
|
|
234
268
|
const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
|
|
235
|
-
return /* @__PURE__ */
|
|
269
|
+
return /* @__PURE__ */ jsx9(Tag, { ref, className: merged, ...props });
|
|
236
270
|
}
|
|
237
271
|
);
|
|
238
272
|
|
|
239
273
|
// src/components/glide.tsx
|
|
240
274
|
import {
|
|
241
|
-
forwardRef as
|
|
242
|
-
useInsertionEffect as
|
|
275
|
+
forwardRef as forwardRef9,
|
|
276
|
+
useInsertionEffect as useInsertionEffect9
|
|
243
277
|
} from "react";
|
|
244
278
|
|
|
245
279
|
// src/primitives/use-presence.ts
|
|
@@ -271,15 +305,15 @@ function usePresence(show) {
|
|
|
271
305
|
}
|
|
272
306
|
|
|
273
307
|
// src/components/glide.tsx
|
|
274
|
-
import { jsx as
|
|
275
|
-
var Glide =
|
|
308
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
309
|
+
var Glide = forwardRef9(
|
|
276
310
|
function Glide2({ show, as: Tag = "div", className, children, ...props }, ref) {
|
|
277
|
-
|
|
311
|
+
useInsertionEffect9(injectStyles, []);
|
|
278
312
|
const { mounted, phase, onAnimationEnd } = usePresence(show);
|
|
279
313
|
if (!mounted) return null;
|
|
280
314
|
const phaseClass = phase === "entering" ? "concertina-glide-entering" : phase === "exiting" ? "concertina-glide-exiting" : "";
|
|
281
315
|
const merged = ["concertina-glide", phaseClass, className].filter(Boolean).join(" ");
|
|
282
|
-
return /* @__PURE__ */
|
|
316
|
+
return /* @__PURE__ */ jsx10(
|
|
283
317
|
Tag,
|
|
284
318
|
{
|
|
285
319
|
ref,
|
|
@@ -336,12 +370,15 @@ export {
|
|
|
336
370
|
Header,
|
|
337
371
|
Hum,
|
|
338
372
|
Item,
|
|
373
|
+
Overture,
|
|
339
374
|
Root,
|
|
340
375
|
Slot,
|
|
341
376
|
Ensemble as StableCollection,
|
|
342
377
|
Bellows as StableSlot,
|
|
343
378
|
Hum as StableText,
|
|
344
379
|
Trigger2 as Trigger,
|
|
380
|
+
Vamp,
|
|
381
|
+
VampContext,
|
|
345
382
|
Warmup,
|
|
346
383
|
WarmupLine,
|
|
347
384
|
pinToScrollTop,
|
|
@@ -352,5 +389,6 @@ export {
|
|
|
352
389
|
useSize,
|
|
353
390
|
useStableSlot,
|
|
354
391
|
useTransitionLock,
|
|
392
|
+
useVamp,
|
|
355
393
|
useWarmupExit
|
|
356
394
|
};
|
package/dist/styles.css
CHANGED
|
@@ -67,12 +67,13 @@
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/* Inactive Slot hiding — belt and suspenders.
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
Primary: inline style on the Slot element (visibility: hidden + opacity: 0).
|
|
71
|
+
Inline styles can't be overridden by any CSS cascade — this is the
|
|
72
|
+
bulletproof layer.
|
|
73
|
+
Backup: CSS rules below catch edge cases (e.g. if inline styles are
|
|
74
|
+
stripped by a framework or test harness).
|
|
72
75
|
transition: none on descendants prevents children with transition-all
|
|
73
|
-
from animating the inherited visibility change
|
|
74
|
-
inherited; transition-all transitions it over the child's duration
|
|
75
|
-
instead of hiding instantly). */
|
|
76
|
+
from animating the inherited visibility change. */
|
|
76
77
|
.concertina-stable-slot > [inert] {
|
|
77
78
|
visibility: hidden;
|
|
78
79
|
opacity: 0;
|