panelset 1.0.8 → 1.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0] - 2026-06-22
4
+
5
+ ### Added
6
+ - PanelControl: a tablist controller with keyboard navigation
7
+ - Web component versions
8
+ - PNavigation on PanelSet: `next()`, `prev()` etc
9
+ - `addPanel()` and `removePanel()`
10
+ - `refresh()`
11
+ - Verb buttons wired by delegation: `data-ps-next`, `data-ps-prev`, `data-ps-close`.
12
+ - Lifecycle events
13
+ - Async content loading
14
+
15
+
16
+ ## [1.0.9] - 2026-04-10
17
+
18
+ ### Added
19
+ - Added Panel
20
+ - New documentation
21
+ - Added destroy() method
22
+ - Added data-panel-trigger implicit wiring (auto ID assignment)
23
+
24
+ ### Fixed
25
+ - Fixed mid-close reversal bug (smooth re-open from mid-animation)
26
+ - Fixed focus jump when closeSiblings closes a sibling
27
+
28
+ ### Removed
29
+ - Removed Core.measure()
30
+
31
+
3
32
 
4
33
  ## [1.0.8] - 2026-02-18
5
34
 
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # PanelSet
2
+
3
+ **Flexible panel management with smooth transitions.**
4
+
5
+ A TypeScript + SCSS library for animating elements between sizes. Three classes share one animation core (a lock / measure / animate / unlock cycle): the browser does the animating, JavaScript only measures and sets pixel values. Accessible by default (managed ARIA and focus), interrupt-safe, and it respects `prefers-reduced-motion`.
6
+
7
+ > **Documentation:** this README is a quick reference. The full guides, every option, and live examples live in the HTML docs. Source and issues: <https://github.com/martinomagnifico/panelset>.
8
+
9
+ ## The three classes
10
+
11
+ | Class | What it does |
12
+ |---|---|
13
+ | **`Panel`** | A single element that opens and closes (accordions, show-more, sidebars, drawers). Animates `height` or `width`. |
14
+ | **`PanelSet`** | A container that switches between mutually exclusive panels (tabs, wizards, steppers). Animates its own height to fit the incoming panel. |
15
+ | **`PanelControl`** | Optional. Makes a tab strip or sidebar drive a `PanelSet`: keyboard navigation, roving `tabindex`, `aria-selected`, and tab locking through `setTabState()`. |
16
+
17
+ `PanelControl` is side-effect-free, so it tree-shakes out of the ESM build when you don’t import it.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install panelset
23
+ ```
24
+
25
+ ```js
26
+ import { Panel, PanelSet, PanelControl } from 'panelset';
27
+ import 'panelset/style.css';
28
+ ```
29
+
30
+ Or as a script tag (IIFE bundle):
31
+
32
+ ```html
33
+ <link rel="stylesheet" href="panelset.css">
34
+ <script src="panelset.js"></script>
35
+ <!-- exposes window.Panel / PanelSet / PanelControl and registers
36
+ <ps-panel> / <ps-panelset> / <ps-panelcontrol> -->
37
+ ```
38
+
39
+ ## Panel
40
+
41
+ Panels are **closed by default** (no class needed). Add `is-open` to start open.
42
+
43
+ ```html
44
+ <button aria-controls="my-panel" aria-expanded="false">Toggle</button>
45
+
46
+ <div id="my-panel" data-panel>
47
+ <div class="panel-wrapper">Content here</div>
48
+ </div>
49
+ ```
50
+
51
+ ```js
52
+ import { Panel } from 'panelset';
53
+ Panel.init();
54
+ ```
55
+
56
+ Any `[aria-controls]` element is a trigger. As a shortcut, a `[data-panel-trigger]` button placed next to a panel (or as the direct child of a heading next to it) gets its `id` and ARIA set up automatically.
57
+
58
+ ### Accordion (group)
59
+
60
+ ```html
61
+ <div data-panel-group data-panel-close-siblings>
62
+ <button aria-controls="acc-1" aria-expanded="false">Item 1</button>
63
+ <div id="acc-1" data-panel><div class="panel-wrapper">Answer 1</div></div>
64
+
65
+ <button aria-controls="acc-2" aria-expanded="false">Item 2</button>
66
+ <div id="acc-2" data-panel><div class="panel-wrapper">Answer 2</div></div>
67
+ </div>
68
+ ```
69
+
70
+ `data-panel-close-siblings` (or `closeSiblings: true`) makes opening one panel close the others.
71
+
72
+ ### Panel options
73
+
74
+ | Option | Type | Default | Description |
75
+ |---|---|---|---|
76
+ | `axis` | `'vertical' \| 'horizontal'` | `'vertical'` | Which dimension animates (height or width) |
77
+ | `align` | `'start' \| 'center' \| 'end'` | `'start'` | Content alignment within the clipped container |
78
+ | `transitions` | `boolean` | `true` | Enable/disable CSS transitions |
79
+ | `autoFocus` | `false \| true \| 'heading' \| 'first' \| 'input'` | `false` | Move focus into the panel on open |
80
+ | `returnFocus` | `boolean` | `true` | Return focus to the trigger on close |
81
+ | `closeSiblings` | `boolean` | `false` | Close other open panels in the same group |
82
+ | `closeOnResize` | `boolean` | `false` | Close the panel when the window is resized |
83
+ | `interruptible` | `boolean` | `true` | Allow a new open/close to interrupt one in progress |
84
+ | `persist` | `boolean` | `false` | Save open/closed state to `localStorage` |
85
+ | `deepLink` | `boolean` | `false` | Reflect open state in the `?panel=` URL |
86
+ | `loadingDelay` | `number` | `320` | ms before the spinner appears during async loading |
87
+ | `loadingHeight` | `number` | `150` | px reserved while async content loads |
88
+ | `debug` | `boolean` | `false` | Log events to the console |
89
+
90
+ ### Async content
91
+
92
+ ```js
93
+ const panel = new Panel('#my-panel');
94
+
95
+ panel.onBeforeOpen((el, signal) => {
96
+ return fetch('/api/content', { signal })
97
+ .then(r => r.text())
98
+ .then(html => { el.querySelector('.panel-wrapper').innerHTML = html; });
99
+ }, { once: true });
100
+ ```
101
+
102
+ Or listen for the `panel:beforeopen` event and call `e.detail.waitUntil(promise)` to hold the open until it resolves.
103
+
104
+ ## PanelSet
105
+
106
+ ```html
107
+ <nav role="tablist" data-panelcontrol>
108
+ <button role="tab" aria-controls="panel-1" aria-selected="true">Tab 1</button>
109
+ <button role="tab" aria-controls="panel-2">Tab 2</button>
110
+ </nav>
111
+
112
+ <div data-panelset>
113
+ <div class="panel-wrapper">
114
+ <div id="panel-1" role="tabpanel" class="active">Content 1</div>
115
+ <div id="panel-2" role="tabpanel" hidden>Content 2</div>
116
+ </div>
117
+ </div>
118
+ ```
119
+
120
+ ```js
121
+ import { PanelSet, PanelControl } from 'panelset';
122
+ PanelSet.init();
123
+ PanelControl.init(); // keyboard navigation + state for the tab strip
124
+ ```
125
+
126
+ Mark the starting panel with `class="active"` and every other panel `hidden`.
127
+
128
+ ### PanelSet options
129
+
130
+ | Option | Type | Default | Description |
131
+ |---|---|---|---|
132
+ | `align` | `'start' \| 'center' \| 'end'` | `'start'` | Alignment while opening/closing |
133
+ | `transitions` | `boolean \| { panels?: boolean, height?: boolean }` | `true` | Enable/disable transitions (or per axis) |
134
+ | `levels` | `boolean` | `false` | Give panels a depth order from DOM position; forward/back slide opposite ways |
135
+ | `loop` | `boolean` | `false` | `next()` / `prev()` wrap around the ends |
136
+ | `closable` | `boolean` | `false` | Allow the whole container to open and close |
137
+ | `closeOnTab` | `boolean` | `false` | Clicking the active tab closes the container (needs `closable`) |
138
+ | `disabledMode` | `'aria' \| 'native'` | `'aria'` | How `data-ps-next` / `-prev` buttons are disabled at the ends |
139
+ | `autoFocus` | `false \| true \| 'heading' \| 'first' \| 'input'` | `false` | Move focus into the panel on activation |
140
+ | `returnFocus` | `boolean` | `false` | Return focus to the trigger on close |
141
+ | `interruptible` | `boolean` | `true` | Allow a new activation to interrupt one in progress |
142
+ | `manageTriggers` | `boolean` | `true` | Reflect `aria-selected` / activating state onto `[aria-controls]` triggers |
143
+ | `manageLabels` | `boolean` | `true` | Link each panel to its tab via `aria-labelledby` (auto-generates a tab id if needed) |
144
+ | `persist` | `boolean` | `false` | Save the active panel id to `localStorage` |
145
+ | `deepLink` | `boolean` | `false` | Reflect the active panel in the `?panel=` URL |
146
+ | `loadingDelay` | `number` | `320` | ms before the spinner appears during async loading |
147
+ | `loadingHeight` | `number` | `150` | px reserved while async content loads |
148
+ | `debug` | `boolean` | `false` | Log events to the console |
149
+
150
+ ### Async content
151
+
152
+ ```js
153
+ const ps = new PanelSet('#my-panelset');
154
+
155
+ ps.onBeforeOpen((targetPanel, signal) => {
156
+ return fetch(`/api/${targetPanel.id}`, { signal })
157
+ .then(r => r.json())
158
+ .then(data => { targetPanel.innerHTML = render(data); });
159
+ }, { once: true });
160
+ ```
161
+
162
+ ## PanelControl
163
+
164
+ Drives one `PanelSet` from its trigger elements, so you do not hand-write the tab interaction. Put `data-panelcontrol` on the container; add `role="tablist"` (with `role="tab"` buttons) to switch on the keyboard model (arrow keys, `Home` / `End`, roving `tabindex`). It finds its `PanelSet` through the panels the triggers point at, so the control can live anywhere in the DOM.
165
+
166
+ Lock or unlock a tab from your own code:
167
+
168
+ ```js
169
+ control.setTabState('panel-3', 'disabled'); // lock
170
+ control.setTabState('panel-3', 'enabled'); // unlock
171
+ ```
172
+
173
+ | Option | Type | Default | Description |
174
+ |---|---|---|---|
175
+ | `activation` | `'manual' \| 'auto'` | `'manual'` | `manual`: arrows move focus, Enter/Space/click activates. `auto`: arrows activate too. |
176
+ | `debug` | `boolean` | `false` | Log to the console |
177
+
178
+ ## Configuration sources
179
+
180
+ Every option can also be set as a **data attribute** in the markup, or as a plain attribute on the **web component**. When the same option is set more than once, the most specific wins: **defaults -> JS options -> data attribute**. The exact attribute names are listed per option in the docs.
181
+
182
+ ## Web components
183
+
184
+ `register()` defines `<ps-panel>`, `<ps-panelset>`, and `<ps-panelcontrol>` (the script-tag build calls it for you). On the elements, options are plain attributes, no `data-` prefix.
185
+
186
+ ```js
187
+ import { register } from 'panelset';
188
+ register(); // default 'ps' prefix
189
+ register('acme'); // <acme-panel>, <acme-panelset>, <acme-panelcontrol>
190
+ ```
191
+
192
+ ## CSS
193
+
194
+ Timing and sizing are CSS custom properties, set on the element or any ancestor (all timing values need a unit, e.g. `0.25s` not `0`). For example, on a Panel:
195
+
196
+ ```css
197
+ [data-panel] {
198
+ --ps-open-speed: 0.25s;
199
+ --ps-open-timing: ease-in-out;
200
+ --ps-close-speed: 0.25s;
201
+ --ps-close-timing: ease-in-out;
202
+ }
203
+ ```
204
+
205
+ `PanelSet` exposes a similar set for its fade and height timing. See the docs for the full list.
206
+
207
+ ## Support
208
+
209
+ PanelSet is free and open source. If it saves you time, consider [sponsoring my work](https://ko-fi.com/martinomagnifico).
210
+
211
+ ## License
212
+
213
+ MIT © [Martinomagnifico](https://github.com/martinomagnifico)