@aiquants/resize-panels 2.2.0 → 2.3.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 +85 -6
- package/dist/{GlobalDebugOverlay-BL40__b0.js → GlobalDebugOverlay-CbqXJA1A.js} +1 -1
- package/dist/{GlobalDebugOverlay-Dp6IcAX8.cjs → GlobalDebugOverlay-R4lLTee4.cjs} +1 -1
- package/dist/index-BLe_thse.js +1500 -0
- package/dist/index-DMDH6fMm.cjs +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +29 -27
- package/dist/labels.d.ts +33 -0
- package/dist/placeholderFocus.d.ts +6 -0
- package/dist/styles/resize-panels.standalone.css +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
- package/dist/index-D8YnyaiG.cjs +0 -2
- package/dist/index-bJcXe02J.js +0 -1379
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Every state change — first paint, container resize, drag, collapse, restored l
|
|
|
14
14
|
- **A click instead of a drag**: give a handle `activationSizes` and a click or tap moves the boundary through those preset sizes — the single-pointer alternative to dragging that WCAG 2.5.7 asks for (see [Clicking instead of dragging](#clicking-instead-of-dragging)).
|
|
15
15
|
- **Auto-Persistence**: set `autoSaveId` to store each panel's open length and collapsed flag in `localStorage` next to its panel id, so a layout saved by a different set of panels is rejected instead of being applied by position.
|
|
16
16
|
- **Layout notifications with a cause**: `onLayout` receives the allocated lengths together with why they changed — a drag, a key, a host call, a container resize, a panel change or a restored layout — so a host can keep what the reader chose apart from what the container forced.
|
|
17
|
-
- **Debug & Visualization**: enable `showDebugInfo` to inspect allocations through the per-panel `PanelDebugInfo` badge and a global overlay (lazily imported; production bundles without it create no per-panel observers at all). A constraint violation needs no flag: the group draws its own placeholder over the panels whenever the allocator reports one
|
|
17
|
+
- **Debug & Visualization**: enable `showDebugInfo` to inspect allocations through the per-panel `PanelDebugInfo` badge and a global overlay (lazily imported; production bundles without it create no per-panel observers at all). A constraint violation needs no flag: the group draws its own [placeholder](#constraint-violation-placeholder) over the panels whenever the allocator reports one, with texts you can replace through `labels`.
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -131,6 +131,7 @@ export const Example = () => (
|
|
|
131
131
|
| `showDebugInfo` | `boolean` | Enables the per-panel `PanelDebugInfo` badge and the global debug overlay |
|
|
132
132
|
| `onLayout` | `(sizes: number[], change: PanelLayoutChange) => void` | Called with the panels' allocated pixel lengths (DOM order) each time they change once the container is measured; `change.source` says why (see [Layout Notifications](#layout-notifications)) |
|
|
133
133
|
| `autoSaveId` | `string` | Key used to automatically persist and restore panel sizes in `localStorage` |
|
|
134
|
+
| `labels` | `PanelGroupLabels` | Texts of the constraint-violation placeholder, in pairs: a reason's summary and detail go together, a reason left out keeps its built-in English pair, and an invalid value or half a pair throws when the group renders (see [Constraint-violation placeholder](#constraint-violation-placeholder)) |
|
|
134
135
|
|
|
135
136
|
### Panel Properties
|
|
136
137
|
|
|
@@ -155,7 +156,9 @@ from `PanelProps` to state that ownership, but TypeScript does not type-check JS
|
|
|
155
156
|
hyphen, so neither it nor a `data-panel-*` / `data-panel-group-*` of your own is rejected at compile time: what
|
|
156
157
|
enforces the contract is the runtime override, which applies these attributes after everything you forwarded. A
|
|
157
158
|
`data-panel-group-id` you pass disappears altogether when the group itself has no `id`. Both elements merge your
|
|
158
|
-
`className` with their own (`panel` / `panel-group`) instead of replacing it.
|
|
159
|
+
`className` with their own (`panel` / `panel-group`) instead of replacing it. While the group reports a constraint
|
|
160
|
+
violation, `Panel` also renders `inert`, whatever you passed: an `inert` of your own applies the rest of the time and
|
|
161
|
+
comes back once the violation ends.
|
|
159
162
|
|
|
160
163
|
### PanelResizeHandle Properties
|
|
161
164
|
|
|
@@ -260,8 +263,9 @@ const LIST_STOPS: readonly FlexibleSize[] = [
|
|
|
260
263
|
- `saveLayout` & `loadLayout`: persist or restore a layout (`{ id, size, collapsed }[]`).
|
|
261
264
|
- `clamp`, `getConstraintInPixels`, `normalizeSizeConfig`, `percentageToPixels`, `toRoundedPercentage`, `calculateSnapThreshold`: the unit conversions and thresholds the components share.
|
|
262
265
|
- `getPanelElement`, `getPanelGroupElement`, `getResizeHandleElement`: DOM element query helpers via `data-*` attributes.
|
|
266
|
+
- `DEFAULT_PANEL_GROUP_LABELS`, `PANEL_GROUP_LABEL_PLACEHOLDERS`: the built-in English texts of the [constraint-violation placeholder](#constraint-violation-placeholder) and the placeholders each text accepts, both frozen.
|
|
263
267
|
|
|
264
|
-
Every public type (`PanelProps`, `PanelGroupProps`, `PanelResizeHandleProps`, `PanelLayoutData`, `PanelRegistration`, `PersistedPanelSize`, `PanelGroupLayoutState`, `PanelAction`, `PanelLayoutChange`, `PanelLayoutSource`, `PanelRequestSource`, `LayoutAllocation`, `PanelBounds`, `ContainerAxisMeasurement`, …) is re-exported from the package root; `docs/specs` (`00-architecture-overview.md` §8) lists the surface in full.
|
|
268
|
+
Every public type (`PanelProps`, `PanelGroupProps`, `PanelResizeHandleProps`, `PanelGroupLabels`, `PanelGroupLabelKey`, `PanelLayoutData`, `PanelRegistration`, `PersistedPanelSize`, `PanelGroupLayoutState`, `PanelAction`, `PanelLayoutChange`, `PanelLayoutSource`, `PanelRequestSource`, `LayoutAllocation`, `PanelBounds`, `ContainerAxisMeasurement`, …) is re-exported from the package root; `docs/specs` (`00-architecture-overview.md` §8) lists the surface in full.
|
|
265
269
|
|
|
266
270
|
## How sizes are decided
|
|
267
271
|
|
|
@@ -284,6 +288,81 @@ violation when the panels' maximums cannot reach the container, and as a logged
|
|
|
284
288
|
full statement, including the proofs of ratio preservation, continuity and the drag fixed point, is in `docs/specs`
|
|
285
289
|
(`11-layout-allocator.md`).
|
|
286
290
|
|
|
291
|
+
## Constraint-violation placeholder
|
|
292
|
+
|
|
293
|
+
When the panels' size constraints cannot fit the container — their minimums add up to more than it, or their
|
|
294
|
+
maximums cannot fill it — the allocator keeps the bounds and reports a violation, and the group draws a placeholder
|
|
295
|
+
over its panels until the container or the panels change enough to resolve it.
|
|
296
|
+
|
|
297
|
+
| Reason | Built-in summary | Built-in detail |
|
|
298
|
+
| --- | --- | --- |
|
|
299
|
+
| The minimums exceed the container | `Layout unavailable: minimum panel sizes exceed the container.` | `Min required: {required}px \| Available: {available}px` |
|
|
300
|
+
| The maximums cannot fill the container | `Layout unavailable: panels cannot fill the container with their maximum sizes.` | `Max total: {total}px \| Needed: {needed}px` |
|
|
301
|
+
|
|
302
|
+
- **Numbers**: `{required}` is the minimums' total rounded up and `{available}` the container's length rounded down;
|
|
303
|
+
`{total}` is the maximums' total rounded down and `{needed}` the container's length rounded up. What the panels
|
|
304
|
+
need is rounded up and what the other side offers is rounded down, so the two numbers shown always keep the strict
|
|
305
|
+
inequality behind the violation.
|
|
306
|
+
- **Labels**: pass `labels` (`PanelGroupLabels`) to replace a reason's texts as a pair — `minimumExceededSummary`
|
|
307
|
+
with `minimumExceededDetail`, and `maximumInsufficientSummary` with `maximumInsufficientDetail`. Pass both texts of
|
|
308
|
+
a pair or neither: a pair you leave out (both keys absent or `undefined`) keeps its built-in English texts from
|
|
309
|
+
`DEFAULT_PANEL_GROUP_LABELS`, and half a pair is rejected, by the type and when the group renders. A detail may use
|
|
310
|
+
only its own placeholders (`PANEL_GROUP_LABEL_PLACEHOLDERS`), may omit or repeat them, and may hold braces only as
|
|
311
|
+
those placeholders; the summaries take none, and braces in a summary are literal text.
|
|
312
|
+
- **Language**: the box carries the language of the pair it shows: `lang="en"` with a built-in pair, and no `lang`
|
|
313
|
+
with a pair you supply, which is read in the page's language even when its texts equal the built-in ones. The
|
|
314
|
+
summary and the detail carry no `lang` of their own and inherit the box's, so the region's name, which comes from
|
|
315
|
+
the summary, is read in the same voice as both lines. To keep a reason in English with `lang="en"`, leave its pair
|
|
316
|
+
out.
|
|
317
|
+
- **Validation**: `labels` is checked when the group renders, whether or not a violation is shown, once per `labels`
|
|
318
|
+
object identity. A value that is not a plain object, or a text that is not a string, throws a `TypeError`; an
|
|
319
|
+
unknown key, a blank text (`text.trim() === ""`), a placeholder the key does not accept, a brace outside a
|
|
320
|
+
placeholder in a detail (`{ required }`, `{{required}}`, a lone `{` or `}`, `{}`; the message gives its index), or
|
|
321
|
+
half a pair (the message names both keys and the one passed) throws a `RangeError`. Every text is checked before
|
|
322
|
+
the pairs are, so a malformed text is reported even when its partner is missing.
|
|
323
|
+
Every message starts with `[resize-panels]`, and your error boundary receives the error.
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import { type PanelGroupLabels, PanelGroup } from "@aiquants/resize-panels"
|
|
327
|
+
|
|
328
|
+
// A module-level constant keeps the identity stable, so the labels are validated once. The maximums' pair is left
|
|
329
|
+
// out, so that reason keeps its built-in English pair.
|
|
330
|
+
const LAYOUT_LABELS: PanelGroupLabels = {
|
|
331
|
+
minimumExceededSummary: "パネルの最小幅の合計が領域に収まりません。",
|
|
332
|
+
minimumExceededDetail: "必要: {required}px / 利用可能: {available}px",
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
<PanelGroup direction="horizontal" labels={LAYOUT_LABELS}>
|
|
336
|
+
{/* ... */}
|
|
337
|
+
</PanelGroup>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
- **Structure**: a translucent veil (`position: absolute`, `inset: 0`, `z-index: 60`, `backdrop-filter: blur(2px)`,
|
|
341
|
+
with the classes `bg-slate-50/82 dark:bg-slate-950/82`) covers the group's padding box and takes the pointer. Inside
|
|
342
|
+
it, a `<section>` named by its summary (implicit role `region`) holds the summary and the detail; hook into it with
|
|
343
|
+
`data-panel-group-placeholder="constraint-violation"`. The `z-index` 60 is above the handles' 20, and above the
|
|
344
|
+
content of a panel that opted out of `isolation: isolate` as long as that content stays below 60.
|
|
345
|
+
- **Layout**: the box's layout is written inline, like every declaration the package's layout depends on, so it holds
|
|
346
|
+
whatever CSS you build. The box fills the veil, scrolls instead of clipping (the group hides overflow), centres its
|
|
347
|
+
text only while the text fits (`safe center`, so overflowing text starts at the scroll origin) and breaks a word
|
|
348
|
+
wider than itself (`overflow-wrap: anywhere`).
|
|
349
|
+
- **Contrast**: the box's surface is opaque, so its text does not depend on what lies under the veil. Computed from
|
|
350
|
+
Tailwind v4's theme, the summary and the detail reach 13.37:1 and 9.44:1 in the light scheme and 11.90:1 and
|
|
351
|
+
9.87:1 in the dark one (at least 7:1, WCAG 1.4.6 AAA); the focus outline reaches at least 6.90:1 against the
|
|
352
|
+
surface and the border (at least 3:1, 1.4.11). Lines are 1.5 high and at most `40ic` long (1.4.8). A Tailwind host
|
|
353
|
+
decides `dark:` with its own dark variant; the standalone CSS follows a `.dark` class on an ancestor.
|
|
354
|
+
- **Focus**: while the placeholder is shown, every `Panel` is `inert` and every handle is hidden and disabled, so
|
|
355
|
+
neither focus nor assistive technology reaches what lies under the veil. If the focus was inside the group when the
|
|
356
|
+
placeholder appeared — in a panel (an SVG element, or an element inside an open shadow root there, included) or on
|
|
357
|
+
a handle, and also when the group itself lives in a shadow root — the box takes it and keeps the element it came
|
|
358
|
+
from; the box is in the tab order only while it exists. When the box goes away still holding the focus and nothing
|
|
359
|
+
else holds it, the focus returns to that element. Focus that left the box during the violation — moved by you or the
|
|
360
|
+
user, or dropped to the body by a click on nothing — stays where it went, and focus that was outside the group is
|
|
361
|
+
never moved. Nested groups share the record: whatever order their violations start and end in, the focus sits on
|
|
362
|
+
the box of the outermost group still broken and returns to the element it came from once every group has mended.
|
|
363
|
+
If that element can no longer take it (removed, disabled), it goes to the box of an enclosing group still broken,
|
|
364
|
+
or nowhere.
|
|
365
|
+
|
|
287
366
|
## Layout Notifications
|
|
288
367
|
|
|
289
368
|
`onLayout(sizes, change)` runs after every commit that replaces the panels' sizes once the container has been
|
|
@@ -363,8 +442,8 @@ size, preference, resolved bounds) along with a global inspector: a header count
|
|
|
363
442
|
handles on the page, then one card per group carrying its direction and container box and, for every panel, the
|
|
364
443
|
state size next to the measured size, both percentages, the preference, the unit, the declared bounds and the two
|
|
365
444
|
priorities. Individual handle measurements and constraint violations are not part of that overlay; a violation is
|
|
366
|
-
shown by the placeholder the group draws over its panels, with or without the
|
|
367
|
-
imported (`React.lazy`); without the flag no per-panel `ResizeObserver` is created.
|
|
445
|
+
shown by the [placeholder](#constraint-violation-placeholder) the group draws over its panels, with or without the
|
|
446
|
+
flag. Debug components are lazily imported (`React.lazy`); without the flag no per-panel `ResizeObserver` is created.
|
|
368
447
|
|
|
369
448
|
## Styling (CSS)
|
|
370
449
|
|
|
@@ -385,7 +464,7 @@ The package provides two CSS consumption options:
|
|
|
385
464
|
@import "@aiquants/resize-panels/styles/resize-panels.standalone.css";
|
|
386
465
|
```
|
|
387
466
|
|
|
388
|
-
Dark mode
|
|
467
|
+
Dark mode applies inside any element carrying the `.dark` class, whether `<html>` or another ancestor.
|
|
389
468
|
|
|
390
469
|
### Style ownership
|
|
391
470
|
|
|
@@ -2,7 +2,7 @@ import { jsx as e, jsxs as t } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState as A } from "react";
|
|
3
3
|
import { createPortal as P } from "react-dom";
|
|
4
4
|
import { useDebugOverlayOwnerId as R, useDebugOverlaySnapshot as T } from "./debugOverlayStore-D7FT6zPE.js";
|
|
5
|
-
import { P as Y } from "./index-
|
|
5
|
+
import { P as Y } from "./index-BLe_thse.js";
|
|
6
6
|
const s = (a, n) => typeof a != "number" || !Number.isFinite(a) ? "—" : `${(a < 0 ? 0 : a).toFixed(8)}${n}`, w = {
|
|
7
7
|
position: "fixed",
|
|
8
8
|
left: "50%",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),A=require("react"),P=require("react-dom"),x=require("./debugOverlayStore-DY09saU8.cjs"),R=require("./index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),A=require("react"),P=require("react-dom"),x=require("./debugOverlayStore-DY09saU8.cjs"),R=require("./index-DMDH6fMm.cjs"),n=(o,i)=>typeof o!="number"||!Number.isFinite(o)?"—":`${(o<0?0:o).toFixed(8)}${i}`,T={position:"fixed",left:"50%",bottom:"20px",transform:"translate(-50%, 0)",zIndex:100,maxWidth:"min(90vw, 1120px)",width:"100%",borderRadius:"14px",backgroundColor:"rgba(15, 23, 42, 0.72)",color:"#e2e8f0",fontFamily:"monospace",fontSize:"11px",lineHeight:1.6,boxShadow:"0 22px 45px rgba(15,23,42,0.45)",border:"1px solid rgba(148, 163, 184, 0.22)",backdropFilter:"blur(10px)",WebkitBackdropFilter:"blur(10px)",pointerEvents:"auto",overflow:"auto"},Y={display:"flex",justifyContent:"space-between",alignItems:"center",flexWrap:"wrap",gap:"12px"},w={display:"flex",flexDirection:"column",gap:"4px"},D={padding:"6px 10px",borderRadius:"9999px",border:"1px solid rgba(148, 163, 184, 0.35)",color:"#e2e8f0",fontWeight:600,fontSize:"10px",letterSpacing:"0.03em",textTransform:"uppercase",cursor:"pointer",transition:"background-color 0.18s ease, border-color 0.18s ease"},C={padding:"10px 12px",borderRadius:"10px",backgroundColor:"rgba(30, 41, 59, 0.55)",border:"1px solid rgba(148, 163, 184, 0.18)",display:"flex",flexDirection:"column",gap:"10px"},N={padding:"8px 10px",borderRadius:"8px",backgroundColor:"rgba(15, 23, 42, 0.55)",border:"1px solid rgba(148, 163, 184, 0.16)",display:"flex",flexDirection:"column",gap:"4px"},p={display:"flex",justifyContent:"space-between",alignItems:"center",gap:"6px"},k={display:"grid",gridTemplateColumns:"auto 1fr",gap:"2px 10px",color:"#e2e8f0"},G={padding:"2px 6px",borderRadius:"9999px",fontSize:"10px",textTransform:"uppercase"},I=({groupId:o})=>x.useDebugOverlayOwnerId()!==o?null:e.jsx(u,{}),u=()=>{const i=x.useDebugOverlaySnapshot().groups,[t,g]=A.useState(!0);if(i.length===0||typeof document>"u")return null;const b=i.reduce((s,a)=>s+a.panels.length,0),h=i.reduce((s,a)=>s+(a.handles?.length??0),0),f=()=>g(s=>!s),m=e.jsxs("div",{style:{...T,padding:t?"10px 18px":"14px 18px",maxHeight:t?"52px":"min(65vh, 520px)",transition:"max-height 0.24s ease, padding 0.24s ease"},children:[e.jsxs("div",{style:{...Y,marginBottom:t?0:"12px"},children:[e.jsxs("div",{style:w,children:[e.jsx("span",{style:{fontWeight:700,fontSize:"12px",letterSpacing:"0.03em"},children:"Panel Overview"}),e.jsxs("span",{style:{color:"#cbd5f5"},children:["groups: ",i.length," / panels: ",b," / resizers: ",h]})]}),e.jsx("button",{type:"button",onClick:f,style:{...D,backgroundColor:t?"rgba(59, 130, 246, 0.2)":"rgba(71, 85, 105, 0.45)"},title:t?"Panel Overview を開く":"Panel Overview を閉じる",children:t?"開く":"閉じる"})]}),!t&&e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"12px",maxHeight:"45vh",overflowY:"auto",paddingRight:"6px"},children:i.map((s,a)=>{const l=s.direction==="horizontal"?s.containerSize.width:s.containerSize.height;return e.jsxs("div",{style:C,children:[e.jsxs("div",{style:{...p,flexWrap:"wrap",gap:"10px",color:"#dbeafe"},children:[e.jsxs("span",{style:{fontWeight:600,fontSize:"11px",letterSpacing:"0.02em",textTransform:"uppercase"},children:["group ",a+1,": ",s.displayName]}),e.jsxs("span",{children:["direction: ",s.direction," / container: ",n(s.containerSize.width,"px")," × ",n(s.containerSize.height,"px")]})]}),e.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:"10px"},children:s.panels.map((r,y)=>{const d=r.measuredPixelSize??r.size,S=r.size,j=n(S,"px"),v=l>0?r.size/l*100:void 0,E=r.measuredPercentageSize!==void 0?r.measuredPercentageSize:l>0?d/l*100:void 0,c=r.collapsed||d<=R.PANEL_SNAP_THRESHOLD,O=r.collapsed?"collapsed":c?"hidden":"visible",z=n(v,"%"),L=n(E,"%"),_=n(d,"px");return e.jsxs("div",{style:N,children:[e.jsxs("div",{style:p,children:[e.jsx("span",{style:{flex:1,minWidth:0,overflow:"hidden",textOverflow:"ellipsis"},children:r.id}),e.jsx("span",{style:{...G,backgroundColor:r.collapsed?"rgba(251, 113, 133, 0.25)":c?"rgba(250, 204, 21, 0.25)":"rgba(74, 222, 128, 0.25)",color:r.collapsed?"#fecdd3":c?"#fef08a":"#bbf7d0"},children:O})]}),e.jsxs("div",{style:k,children:[e.jsx("span",{children:"size (state):"}),e.jsx("span",{children:j}),e.jsx("span",{children:"size (measured):"}),e.jsx("span",{children:_}),e.jsx("span",{children:"percentage (state):"}),e.jsx("span",{children:z}),e.jsx("span",{children:"preference:"}),e.jsx("span",{children:r.sizeUnit==="pixels"?n(r.preferredPixelSize,"px"):n(r.preferredPercentageSize,"%")}),e.jsx("span",{children:"percentage (measured):"}),e.jsx("span",{children:L}),e.jsx("span",{children:"unit:"}),e.jsx("span",{children:r.sizeUnit}),e.jsx("span",{children:"min:"}),e.jsx("span",{children:r.minSize!==void 0?JSON.stringify(r.minSize):"—"}),e.jsx("span",{children:"max:"}),e.jsx("span",{children:r.maxSize!==void 0?JSON.stringify(r.maxSize):"—"}),e.jsx("span",{children:"priority:"}),e.jsx("span",{children:r.pixelAdjustPriority??"—"}),e.jsx("span",{children:"flex priority:"}),e.jsx("span",{children:r.flexAdjustPriority??"—"})]})]},r.id||y)})})]},s.groupId)})})]});return P.createPortal(m,document.body)};exports.GlobalDebugOverlay=u;exports.GlobalDebugOverlayGate=I;
|