concertina 0.12.0 → 0.13.1

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 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 three high-level components: **Bellows**, **Hum**, and **Ensemble**. They handle the math so you can focus on the music. CSS is auto-injected on first render. No manual imports needed.
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
@@ -225,6 +225,34 @@ Named after musical **vamping** — repeating a pattern while waiting for a cue.
225
225
 
226
226
  ---
227
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
+
228
256
  ## Ensemble: temporal stability for collections
229
257
 
230
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.
@@ -408,6 +436,7 @@ Scrolls an element to the top of its nearest scrollable ancestor. Only touches `
408
436
  | Two variants swap in one slot | Bellows + Slot |
409
437
  | Line of text loading from API | Hum |
410
438
  | Many Hum instances share one loading state | Vamp + Hum |
439
+ | Card/table/page loading from API | Overture + Hum |
411
440
  | List loading from API | Ensemble |
412
441
  | Spinner replaced by loaded content | Gigbag + Warmup |
413
442
  | Accordion/table shimmer rows | Stub data + WarmupLine (wrapper-once pattern) |
@@ -1351,6 +1351,7 @@ function injectStyles() {
1351
1351
  document.head.appendChild(style);
1352
1352
  injected = true;
1353
1353
  }
1354
+ injectStyles();
1354
1355
 
1355
1356
  // src/accordion/content.tsx
1356
1357
  var import_jsx_runtime11 = require("react/jsx-runtime");
package/dist/accordion.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  Trigger2,
9
9
  useConcertina,
10
10
  useExpanded
11
- } from "./chunk-OGJMPKZX.js";
11
+ } from "./chunk-6UMIJ4S7.js";
12
12
  export {
13
13
  ConcertinaContext,
14
14
  ConcertinaStore,
@@ -1312,6 +1312,7 @@ function injectStyles() {
1312
1312
  document.head.appendChild(style);
1313
1313
  injected = true;
1314
1314
  }
1315
+ injectStyles();
1315
1316
 
1316
1317
  // src/accordion/content.tsx
1317
1318
  import { jsx as jsx10 } from "react/jsx-runtime";
package/dist/index.cjs CHANGED
@@ -40,6 +40,7 @@ __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,
@@ -1372,6 +1373,7 @@ function injectStyles() {
1372
1373
  document.head.appendChild(style);
1373
1374
  injected = true;
1374
1375
  }
1376
+ injectStyles();
1375
1377
 
1376
1378
  // src/accordion/content.tsx
1377
1379
  var import_jsx_runtime11 = require("react/jsx-runtime");
@@ -1437,9 +1439,6 @@ var Slot = (0, import_react12.forwardRef)(
1437
1439
  }
1438
1440
  );
1439
1441
 
1440
- // src/components/hum.tsx
1441
- var import_react14 = require("react");
1442
-
1443
1442
  // src/components/vamp.tsx
1444
1443
  var import_react13 = require("react");
1445
1444
  var import_jsx_runtime14 = require("react/jsx-runtime");
@@ -1453,34 +1452,31 @@ function Vamp({ loading, children }) {
1453
1452
 
1454
1453
  // src/components/hum.tsx
1455
1454
  var import_jsx_runtime15 = require("react/jsx-runtime");
1456
- var Hum = (0, import_react14.forwardRef)(
1457
- function Hum2({ loading, as: Tag = "span", className, children, ...props }, ref) {
1458
- (0, import_react14.useInsertionEffect)(injectStyles, []);
1459
- const vampLoading = useVamp();
1460
- const isLoading = loading ?? vampLoading;
1461
- if (isLoading) {
1462
- const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
1463
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { ref, className: merged, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { inert: true, children }) });
1464
- }
1465
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { ref, className, ...props, children });
1455
+ function Hum({ loading, as: Tag = "span", className, children, ...props }) {
1456
+ const vampLoading = useVamp();
1457
+ const isLoading = loading ?? vampLoading;
1458
+ if (isLoading) {
1459
+ const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
1460
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { className: merged, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { inert: true, children }) });
1466
1461
  }
1467
- );
1462
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Tag, { className, ...props, children });
1463
+ }
1468
1464
 
1469
- // src/components/ensemble.tsx
1470
- var import_react19 = require("react");
1465
+ // src/components/overture.tsx
1466
+ var import_react17 = require("react");
1471
1467
 
1472
1468
  // src/components/gigbag.tsx
1473
- var import_react16 = require("react");
1469
+ var import_react15 = require("react");
1474
1470
 
1475
1471
  // src/primitives/use-stable-slot.ts
1476
- var import_react15 = require("react");
1472
+ var import_react14 = require("react");
1477
1473
  var RATCHET_FLOOR = -Infinity;
1478
1474
  function useStableSlot(options = {}) {
1479
1475
  const { axis = "both" } = options;
1480
- const [style, setStyle] = (0, import_react15.useState)({});
1481
- const maxRef = (0, import_react15.useRef)({ w: RATCHET_FLOOR, h: RATCHET_FLOOR });
1482
- const observerRef = (0, import_react15.useRef)(null);
1483
- const ref = (0, import_react15.useCallback)(
1476
+ const [style, setStyle] = (0, import_react14.useState)({});
1477
+ const maxRef = (0, import_react14.useRef)({ w: RATCHET_FLOOR, h: RATCHET_FLOOR });
1478
+ const observerRef = (0, import_react14.useRef)(null);
1479
+ const ref = (0, import_react14.useCallback)(
1484
1480
  (el) => {
1485
1481
  if (observerRef.current) {
1486
1482
  observerRef.current.disconnect();
@@ -1528,9 +1524,9 @@ function useStableSlot(options = {}) {
1528
1524
 
1529
1525
  // src/components/gigbag.tsx
1530
1526
  var import_jsx_runtime16 = require("react/jsx-runtime");
1531
- var Gigbag = (0, import_react16.forwardRef)(
1527
+ var Gigbag = (0, import_react15.forwardRef)(
1532
1528
  function Gigbag2({ axis = "height", as: Tag = "div", className, style, children, ...props }, fwdRef) {
1533
- (0, import_react16.useInsertionEffect)(injectStyles, []);
1529
+ (0, import_react15.useInsertionEffect)(injectStyles, []);
1534
1530
  const { ref: ratchetRef, style: ratchetStyle } = useStableSlot({ axis });
1535
1531
  const merged = className ? `concertina-gigbag ${className}` : "concertina-gigbag";
1536
1532
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
@@ -1546,38 +1542,12 @@ var Gigbag = (0, import_react16.forwardRef)(
1546
1542
  }
1547
1543
  );
1548
1544
 
1549
- // src/components/warmup.tsx
1550
- var import_react17 = require("react");
1551
- var import_jsx_runtime17 = require("react/jsx-runtime");
1552
- var Warmup = (0, import_react17.forwardRef)(
1553
- function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
1554
- (0, import_react17.useInsertionEffect)(injectStyles, []);
1555
- const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
1556
- const count2 = columns ? rows * columns : rows;
1557
- const cells = Array.from({ length: count2 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "concertina-warmup-bone", children: [
1558
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "concertina-warmup-line" }),
1559
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "concertina-warmup-line" })
1560
- ] }, i));
1561
- const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
1562
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1563
- Tag,
1564
- {
1565
- ref,
1566
- className: merged,
1567
- style: gridStyle,
1568
- ...props,
1569
- children: cells
1570
- }
1571
- );
1572
- }
1573
- );
1574
-
1575
1545
  // src/primitives/use-warmup-exit.ts
1576
- var import_react18 = require("react");
1546
+ var import_react16 = require("react");
1577
1547
  function useWarmupExit(loading, duration) {
1578
- const [exiting, setExiting] = (0, import_react18.useState)(false);
1579
- const prevLoading = (0, import_react18.useRef)(loading);
1580
- (0, import_react18.useEffect)(() => {
1548
+ const [exiting, setExiting] = (0, import_react16.useState)(false);
1549
+ const prevLoading = (0, import_react16.useRef)(loading);
1550
+ (0, import_react16.useEffect)(() => {
1581
1551
  if (prevLoading.current && !loading) {
1582
1552
  setExiting(true);
1583
1553
  const id = setTimeout(() => setExiting(false), duration);
@@ -1594,8 +1564,48 @@ function useWarmupExit(loading, duration) {
1594
1564
  };
1595
1565
  }
1596
1566
 
1567
+ // src/components/overture.tsx
1568
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1569
+ var Overture = (0, import_react17.forwardRef)(
1570
+ function Overture2({ loading, exitDuration, as: Tag = "div", className, children, ...props }, ref) {
1571
+ (0, import_react17.useInsertionEffect)(injectStyles, []);
1572
+ const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
1573
+ const merged = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
1574
+ 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 }) });
1575
+ }
1576
+ );
1577
+
1597
1578
  // src/components/ensemble.tsx
1579
+ var import_react19 = require("react");
1580
+
1581
+ // src/components/warmup.tsx
1582
+ var import_react18 = require("react");
1598
1583
  var import_jsx_runtime18 = require("react/jsx-runtime");
1584
+ var Warmup = (0, import_react18.forwardRef)(
1585
+ function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
1586
+ (0, import_react18.useInsertionEffect)(injectStyles, []);
1587
+ const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
1588
+ const count2 = columns ? rows * columns : rows;
1589
+ const cells = Array.from({ length: count2 }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "concertina-warmup-bone", children: [
1590
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "concertina-warmup-line" }),
1591
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "concertina-warmup-line" })
1592
+ ] }, i));
1593
+ const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
1594
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1595
+ Tag,
1596
+ {
1597
+ ref,
1598
+ className: merged,
1599
+ style: gridStyle,
1600
+ ...props,
1601
+ children: cells
1602
+ }
1603
+ );
1604
+ }
1605
+ );
1606
+
1607
+ // src/components/ensemble.tsx
1608
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1599
1609
  function EnsembleInner({
1600
1610
  items,
1601
1611
  loading,
@@ -1609,18 +1619,18 @@ function EnsembleInner({
1609
1619
  (0, import_react19.useInsertionEffect)(injectStyles, []);
1610
1620
  const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
1611
1621
  const warmupClass = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
1612
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Gigbag, { ref, axis: "height", as: Tag, ...props, children: showWarmup ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Warmup, { rows: stubCount, className: warmupClass }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Tag, { className, children: items.map(renderItem) }) });
1622
+ 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) }) });
1613
1623
  }
1614
1624
  var Ensemble = (0, import_react19.forwardRef)(EnsembleInner);
1615
1625
 
1616
1626
  // src/components/warmup-line.tsx
1617
1627
  var import_react20 = require("react");
1618
- var import_jsx_runtime19 = require("react/jsx-runtime");
1628
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1619
1629
  var WarmupLine = (0, import_react20.forwardRef)(
1620
1630
  function WarmupLine2({ as: Tag = "div", className, ...props }, ref) {
1621
1631
  (0, import_react20.useInsertionEffect)(injectStyles, []);
1622
1632
  const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
1623
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Tag, { ref, className: merged, ...props });
1633
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Tag, { ref, className: merged, ...props });
1624
1634
  }
1625
1635
  );
1626
1636
 
@@ -1652,7 +1662,7 @@ function usePresence2(show) {
1652
1662
  }
1653
1663
 
1654
1664
  // src/components/glide.tsx
1655
- var import_jsx_runtime20 = require("react/jsx-runtime");
1665
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1656
1666
  var Glide = (0, import_react22.forwardRef)(
1657
1667
  function Glide2({ show, as: Tag = "div", className, children, ...props }, ref) {
1658
1668
  (0, import_react22.useInsertionEffect)(injectStyles, []);
@@ -1660,7 +1670,7 @@ var Glide = (0, import_react22.forwardRef)(
1660
1670
  if (!mounted) return null;
1661
1671
  const phaseClass = phase === "entering" ? "concertina-glide-entering" : phase === "exiting" ? "concertina-glide-exiting" : "";
1662
1672
  const merged = ["concertina-glide", phaseClass, className].filter(Boolean).join(" ");
1663
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1673
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1664
1674
  Tag,
1665
1675
  {
1666
1676
  ref,
@@ -1757,6 +1767,7 @@ function useConcertina() {
1757
1767
  Header,
1758
1768
  Hum,
1759
1769
  Item,
1770
+ Overture,
1760
1771
  Root,
1761
1772
  Slot,
1762
1773
  StableCollection,
package/dist/index.d.cts CHANGED
@@ -65,7 +65,7 @@ interface HumProps extends HTMLAttributes<HTMLElement> {
65
65
  * nearest `<Vamp>` ancestor. This lets a single provider control
66
66
  * shimmer state for an entire subtree.
67
67
  */
68
- declare const Hum: react.ForwardRefExoticComponent<HumProps & react.RefAttributes<HTMLElement>>;
68
+ declare function Hum({ loading, as: Tag, className, children, ...props }: HumProps): react_jsx_runtime.JSX.Element;
69
69
 
70
70
  /** Context carrying the ambient loading state set by Vamp. */
71
71
  declare const VampContext: react.Context<boolean>;
@@ -89,6 +89,34 @@ interface VampProps {
89
89
  */
90
90
  declare function Vamp({ loading, children }: VampProps): react_jsx_runtime.JSX.Element;
91
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
+
92
120
  interface EnsembleProps<T> extends Omit<HTMLAttributes<HTMLElement>, "children"> {
93
121
  /** Data items to render. */
94
122
  items: T[];
@@ -316,4 +344,4 @@ declare function useWarmupExit(loading: boolean, duration: number): {
316
344
  exiting: boolean;
317
345
  };
318
346
 
319
- 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, Vamp, VampContext, type VampProps, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useVamp, 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
@@ -65,7 +65,7 @@ interface HumProps extends HTMLAttributes<HTMLElement> {
65
65
  * nearest `<Vamp>` ancestor. This lets a single provider control
66
66
  * shimmer state for an entire subtree.
67
67
  */
68
- declare const Hum: react.ForwardRefExoticComponent<HumProps & react.RefAttributes<HTMLElement>>;
68
+ declare function Hum({ loading, as: Tag, className, children, ...props }: HumProps): react_jsx_runtime.JSX.Element;
69
69
 
70
70
  /** Context carrying the ambient loading state set by Vamp. */
71
71
  declare const VampContext: react.Context<boolean>;
@@ -89,6 +89,34 @@ interface VampProps {
89
89
  */
90
90
  declare function Vamp({ loading, children }: VampProps): react_jsx_runtime.JSX.Element;
91
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
+
92
120
  interface EnsembleProps<T> extends Omit<HTMLAttributes<HTMLElement>, "children"> {
93
121
  /** Data items to render. */
94
122
  items: T[];
@@ -316,4 +344,4 @@ declare function useWarmupExit(loading: boolean, duration: number): {
316
344
  exiting: boolean;
317
345
  };
318
346
 
319
- 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, Vamp, VampContext, type VampProps, Warmup, WarmupLine, type WarmupLineProps, type WarmupProps, pinToScrollTop, usePresence, useScrollPin, useSize, useStableSlot, useTransitionLock, useVamp, 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-OGJMPKZX.js";
16
+ } from "./chunk-6UMIJ4S7.js";
17
17
 
18
18
  // src/components/bellows.tsx
19
19
  import {
@@ -60,12 +60,6 @@ var Slot = forwardRef2(
60
60
  }
61
61
  );
62
62
 
63
- // src/components/hum.tsx
64
- import {
65
- forwardRef as forwardRef3,
66
- useInsertionEffect as useInsertionEffect3
67
- } from "react";
68
-
69
63
  // src/components/vamp.tsx
70
64
  import { createContext as createContext2, useContext as useContext2 } from "react";
71
65
  import { jsx as jsx3 } from "react/jsx-runtime";
@@ -79,27 +73,24 @@ function Vamp({ loading, children }) {
79
73
 
80
74
  // src/components/hum.tsx
81
75
  import { jsx as jsx4 } from "react/jsx-runtime";
82
- var Hum = forwardRef3(
83
- function Hum2({ loading, as: Tag = "span", className, children, ...props }, ref) {
84
- useInsertionEffect3(injectStyles, []);
85
- const vampLoading = useVamp();
86
- const isLoading = loading ?? vampLoading;
87
- if (isLoading) {
88
- const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
89
- return /* @__PURE__ */ jsx4(Tag, { ref, className: merged, ...props, children: /* @__PURE__ */ jsx4(Tag, { inert: true, children }) });
90
- }
91
- return /* @__PURE__ */ jsx4(Tag, { ref, className, ...props, children });
76
+ function Hum({ loading, as: Tag = "span", className, children, ...props }) {
77
+ const vampLoading = useVamp();
78
+ const isLoading = loading ?? vampLoading;
79
+ if (isLoading) {
80
+ const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
81
+ return /* @__PURE__ */ jsx4(Tag, { className: merged, ...props, children: /* @__PURE__ */ jsx4(Tag, { inert: true, children }) });
92
82
  }
93
- );
83
+ return /* @__PURE__ */ jsx4(Tag, { className, ...props, children });
84
+ }
94
85
 
95
- // src/components/ensemble.tsx
86
+ // src/components/overture.tsx
96
87
  import {
97
- forwardRef as forwardRef6,
98
- useInsertionEffect as useInsertionEffect6
88
+ forwardRef as forwardRef4,
89
+ useInsertionEffect as useInsertionEffect4
99
90
  } from "react";
100
91
 
101
92
  // src/components/gigbag.tsx
102
- import { forwardRef as forwardRef4, useInsertionEffect as useInsertionEffect4 } from "react";
93
+ import { forwardRef as forwardRef3, useInsertionEffect as useInsertionEffect3 } from "react";
103
94
 
104
95
  // src/primitives/use-stable-slot.ts
105
96
  import { useState, useCallback, useRef } from "react";
@@ -157,9 +148,9 @@ function useStableSlot(options = {}) {
157
148
 
158
149
  // src/components/gigbag.tsx
159
150
  import { jsx as jsx5 } from "react/jsx-runtime";
160
- var Gigbag = forwardRef4(
151
+ var Gigbag = forwardRef3(
161
152
  function Gigbag2({ axis = "height", as: Tag = "div", className, style, children, ...props }, fwdRef) {
162
- useInsertionEffect4(injectStyles, []);
153
+ useInsertionEffect3(injectStyles, []);
163
154
  const { ref: ratchetRef, style: ratchetStyle } = useStableSlot({ axis });
164
155
  const merged = className ? `concertina-gigbag ${className}` : "concertina-gigbag";
165
156
  return /* @__PURE__ */ jsx5(
@@ -175,32 +166,6 @@ var Gigbag = forwardRef4(
175
166
  }
176
167
  );
177
168
 
178
- // src/components/warmup.tsx
179
- import { forwardRef as forwardRef5, useInsertionEffect as useInsertionEffect5 } from "react";
180
- import { jsx as jsx6, jsxs } from "react/jsx-runtime";
181
- var Warmup = forwardRef5(
182
- function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
183
- useInsertionEffect5(injectStyles, []);
184
- const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
185
- const count = columns ? rows * columns : rows;
186
- const cells = Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "concertina-warmup-bone", children: [
187
- /* @__PURE__ */ jsx6("div", { className: "concertina-warmup-line" }),
188
- /* @__PURE__ */ jsx6("div", { className: "concertina-warmup-line" })
189
- ] }, i));
190
- const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
191
- return /* @__PURE__ */ jsx6(
192
- Tag,
193
- {
194
- ref,
195
- className: merged,
196
- style: gridStyle,
197
- ...props,
198
- children: cells
199
- }
200
- );
201
- }
202
- );
203
-
204
169
  // src/primitives/use-warmup-exit.ts
205
170
  import { useState as useState2, useEffect, useRef as useRef2 } from "react";
206
171
  function useWarmupExit(loading, duration) {
@@ -223,8 +188,51 @@ function useWarmupExit(loading, duration) {
223
188
  };
224
189
  }
225
190
 
191
+ // src/components/overture.tsx
192
+ import { jsx as jsx6 } from "react/jsx-runtime";
193
+ var Overture = forwardRef4(
194
+ function Overture2({ loading, exitDuration, as: Tag = "div", className, children, ...props }, ref) {
195
+ useInsertionEffect4(injectStyles, []);
196
+ const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
197
+ const merged = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
198
+ return /* @__PURE__ */ jsx6(Gigbag, { ref, axis: "height", as: Tag, className: merged, ...props, children: /* @__PURE__ */ jsx6(Vamp, { loading: showWarmup, children }) });
199
+ }
200
+ );
201
+
202
+ // src/components/ensemble.tsx
203
+ import {
204
+ forwardRef as forwardRef6,
205
+ useInsertionEffect as useInsertionEffect6
206
+ } from "react";
207
+
208
+ // src/components/warmup.tsx
209
+ import { forwardRef as forwardRef5, useInsertionEffect as useInsertionEffect5 } from "react";
210
+ import { jsx as jsx7, jsxs } from "react/jsx-runtime";
211
+ var Warmup = forwardRef5(
212
+ function Warmup2({ rows, columns, as: Tag = "div", className, children, ...props }, ref) {
213
+ useInsertionEffect5(injectStyles, []);
214
+ const merged = className ? `concertina-warmup ${className}` : "concertina-warmup";
215
+ const count = columns ? rows * columns : rows;
216
+ const cells = Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "concertina-warmup-bone", children: [
217
+ /* @__PURE__ */ jsx7("div", { className: "concertina-warmup-line" }),
218
+ /* @__PURE__ */ jsx7("div", { className: "concertina-warmup-line" })
219
+ ] }, i));
220
+ const gridStyle = columns ? { gridTemplateColumns: `repeat(${columns}, auto)`, gridTemplateAreas: `'${"chamber ".repeat(columns).trim()}'` } : { gridTemplateAreas: "'chamber'" };
221
+ return /* @__PURE__ */ jsx7(
222
+ Tag,
223
+ {
224
+ ref,
225
+ className: merged,
226
+ style: gridStyle,
227
+ ...props,
228
+ children: cells
229
+ }
230
+ );
231
+ }
232
+ );
233
+
226
234
  // src/components/ensemble.tsx
227
- import { jsx as jsx7 } from "react/jsx-runtime";
235
+ import { jsx as jsx8 } from "react/jsx-runtime";
228
236
  function EnsembleInner({
229
237
  items,
230
238
  loading,
@@ -238,18 +246,18 @@ function EnsembleInner({
238
246
  useInsertionEffect6(injectStyles, []);
239
247
  const { showWarmup, exiting } = useWarmupExit(loading, exitDuration);
240
248
  const warmupClass = exiting ? className ? `concertina-warmup-exiting ${className}` : "concertina-warmup-exiting" : className;
241
- return /* @__PURE__ */ jsx7(Gigbag, { ref, axis: "height", as: Tag, ...props, children: showWarmup ? /* @__PURE__ */ jsx7(Warmup, { rows: stubCount, className: warmupClass }) : /* @__PURE__ */ jsx7(Tag, { className, children: items.map(renderItem) }) });
249
+ 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) }) });
242
250
  }
243
251
  var Ensemble = forwardRef6(EnsembleInner);
244
252
 
245
253
  // src/components/warmup-line.tsx
246
254
  import { forwardRef as forwardRef7, useInsertionEffect as useInsertionEffect7 } from "react";
247
- import { jsx as jsx8 } from "react/jsx-runtime";
255
+ import { jsx as jsx9 } from "react/jsx-runtime";
248
256
  var WarmupLine = forwardRef7(
249
257
  function WarmupLine2({ as: Tag = "div", className, ...props }, ref) {
250
258
  useInsertionEffect7(injectStyles, []);
251
259
  const merged = className ? `concertina-warmup-line ${className}` : "concertina-warmup-line";
252
- return /* @__PURE__ */ jsx8(Tag, { ref, className: merged, ...props });
260
+ return /* @__PURE__ */ jsx9(Tag, { ref, className: merged, ...props });
253
261
  }
254
262
  );
255
263
 
@@ -288,7 +296,7 @@ function usePresence(show) {
288
296
  }
289
297
 
290
298
  // src/components/glide.tsx
291
- import { jsx as jsx9 } from "react/jsx-runtime";
299
+ import { jsx as jsx10 } from "react/jsx-runtime";
292
300
  var Glide = forwardRef8(
293
301
  function Glide2({ show, as: Tag = "div", className, children, ...props }, ref) {
294
302
  useInsertionEffect8(injectStyles, []);
@@ -296,7 +304,7 @@ var Glide = forwardRef8(
296
304
  if (!mounted) return null;
297
305
  const phaseClass = phase === "entering" ? "concertina-glide-entering" : phase === "exiting" ? "concertina-glide-exiting" : "";
298
306
  const merged = ["concertina-glide", phaseClass, className].filter(Boolean).join(" ");
299
- return /* @__PURE__ */ jsx9(
307
+ return /* @__PURE__ */ jsx10(
300
308
  Tag,
301
309
  {
302
310
  ref,
@@ -353,6 +361,7 @@ export {
353
361
  Header,
354
362
  Hum,
355
363
  Item,
364
+ Overture,
356
365
  Root,
357
366
  Slot,
358
367
  Ensemble as StableCollection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "concertina",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "React toolkit for layout stability.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",