@threadlabs/looma 0.23.6 → 0.23.8

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.
@@ -7,10 +7,12 @@
7
7
  >Which colour the badge speaks in. neutral is a quiet grey for plain labels; accent draws the eye in the accent colour; info, success, warning, and danger
8
8
  mark a status, so match the tone to what the badge says. For a category rather than a status, set the --ui-badge-color hook on the badge instead: any CSS
9
9
  colour, which overrides the tone.</prop>
10
- <prop name="shape" type="keyword" values="pill, tag, dot" default="pill"
10
+ <prop name="shape" type="keyword" values="pill, tag, dot, square" default="pill"
11
11
  >Silhouette. pill is rounded at both ends; tag is a pointed label. dot is a compact circular signal with visually hidden text: include a state label, or set
12
- aria-hidden when the surrounding control already names that state.</prop>
13
- <prop name="size" type="keyword" values="xs, md" default="md">Scale. xs is a compact count beside small controls; md is the usual text badge.</prop>
12
+ aria-hidden when the surrounding control already names that state. square is a fixed rounded square for an icon or single glyph beside a heading; hide a
13
+ decorative icon when adjacent words name it.</prop>
14
+ <prop name="size" type="keyword" values="xs, md" default="md"
15
+ >Scale. xs is a compact count beside small controls; md is the usual text badge. A square is 24px at xs and 32px at md with default spacing.</prop>
14
16
  </defs>
15
17
  <span><span class="label"><slot></slot></span></span>
16
18
  <style>
@@ -19,6 +21,10 @@
19
21
  syntax: "*";
20
22
  inherits: false;
21
23
  }
24
+ @property --ui-badge-square-size {
25
+ syntax: "*";
26
+ inherits: false;
27
+ }
22
28
  @property --ui-badge-dot-size {
23
29
  syntax: "*";
24
30
  inherits: false;
@@ -221,6 +227,31 @@
221
227
  --_badge-text: var(--ui-info-subtle-text, var(--ui-info-solid));
222
228
  }
223
229
 
230
+ /* An icon mark keeps the existing tone recipe and follows the global spacing and radius. */
231
+ :host-state([shape="square"]) {
232
+ --_badge-square-size: var(--ui-badge-square-size, calc(var(--ui-space-4) * 2));
233
+ display: inline-flex;
234
+ box-sizing: border-box;
235
+ flex: none;
236
+ inline-size: var(--_badge-square-size);
237
+ block-size: var(--_badge-square-size);
238
+ min-inline-size: var(--_badge-square-size);
239
+ min-block-size: var(--_badge-square-size);
240
+ padding: 0;
241
+ border-radius: var(--ui-radius-md);
242
+ text-box-trim: none;
243
+ }
244
+
245
+ :host-state([shape="square"][size="xs"]) {
246
+ --_badge-square-size: var(--ui-badge-square-size, calc(var(--ui-space-3) * 2));
247
+ }
248
+
249
+ :host-state([shape="square"]) .label {
250
+ display: inline-flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ }
254
+
224
255
  /* A dot uses the badge's tone recipe; only its silhouette and visible label change. */
225
256
  :host-state([shape="dot"]) {
226
257
  position: relative;
@@ -2,6 +2,11 @@
2
2
  <defs>
3
3
  <prop name="density" type="keyword" values="comfortable, compact" default="comfortable"
4
4
  >Compact trades padding and type size for rows that fit more on screen.</prop>
5
+ <prop name="variant" type="keyword" values="plain, navigation" default="plain"
6
+ >Navigation shares icon columns, spacing, type, hover, and corners with Nav Item. Plain retains the standard disclosure treatment.</prop>
7
+ <prop name="requiredOpen" type="boolean" default="false"
8
+ >In a named exclusive group, keeps at least one member open. Clicking the last open trigger does not close it; opening another member transfers the open
9
+ state.</prop>
5
10
  <prop name="fill" type="boolean" default="false"
6
11
  >In a column with a bounded height, an open disclosure fills the remaining space and keeps its trigger visible. The content slot can contain a Scroll Area.
7
12
  Closed items keep only their trigger height.</prop>
@@ -38,10 +43,11 @@
38
43
  </event>
39
44
  </defs>
40
45
  <div>
41
- <div class="heading" from:role="headingRole" from:aria-level="headingAriaLevel">
46
+ <div class="heading" $ref="heading" from:role="headingRole" from:aria-level="headingAriaLevel">
42
47
  <button class="trigger" type="button" $ref="trigger" from:aria-controls="contentId" from:aria-expanded="internalOpen" from:disabled="disabled">
48
+ <span class="leading"><slot name="leading"></slot></span>
43
49
  <span class="summary"><slot name="summary"><template $value="summary"></template></slot></span>
44
- <slot name="indicator"><span class="chevron" aria-hidden="true"></span></slot>
50
+ <span class="indicator" aria-hidden="true"><slot name="indicator"><span class="chevron"></span></slot></span>
45
51
  </button>
46
52
  </div>
47
53
  <div class="panel" $ref="panel" from:id="contentId"><div class="panel-inner"><slot></slot></div></div>
@@ -90,8 +96,7 @@
90
96
  }
91
97
 
92
98
  .trigger {
93
- display: grid;
94
- grid-template-columns: minmax(0, 1fr) auto;
99
+ display: flex;
95
100
  gap: var(--ui-space-3);
96
101
  align-items: center;
97
102
  inline-size: 100%;
@@ -121,19 +126,40 @@
121
126
  cursor: not-allowed;
122
127
  }
123
128
 
129
+ .summary {
130
+ flex: 1;
131
+ min-inline-size: 0;
132
+ }
133
+
134
+ .leading,
135
+ .indicator {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ flex: none;
140
+ }
141
+ .leading:empty {
142
+ display: none;
143
+ }
144
+ .indicator {
145
+ transition: transform var(--ui-motion-feedback) var(--ui-motion-feedback-ease);
146
+ }
147
+ :host-state([internalOpen]) .indicator {
148
+ transform: rotate(180deg);
149
+ }
150
+
124
151
  .chevron {
125
152
  inline-size: 0.5rem;
126
153
  block-size: 0.5rem;
127
154
  border-inline-end: 2px solid currentColor;
128
155
  border-block-end: 2px solid currentColor;
129
156
  transform: rotate(45deg) translate(-1px, -1px);
130
- transition: transform var(--ui-motion-fast) var(--ui-motion-ease);
131
157
  }
132
158
 
133
159
  .panel {
134
160
  display: grid;
135
161
  grid-template-rows: 0fr;
136
- transition: grid-template-rows var(--ui-motion-fast) var(--ui-motion-ease-out, var(--ui-motion-ease));
162
+ transition: grid-template-rows var(--ui-motion-reveal) var(--ui-motion-reveal-ease), content-visibility var(--ui-motion-reveal) allow-discrete;
137
163
  }
138
164
 
139
165
  .panel-inner {
@@ -148,22 +174,20 @@
148
174
  :host-state([internalOpen]) .panel-inner {
149
175
  padding-block: 0 var(--ui-space-3);
150
176
  }
151
- :host-state([internalOpen]) .chevron {
152
- transform: rotate(225deg) translate(-1px, -1px);
153
- }
154
177
 
155
178
  /* A bounded accordion owns one flexible body, so its trigger never scrolls away. The
156
179
  * normal content-sized layout and its animation remain unchanged when fill is omitted. */
157
180
  :host-state([fill]) {
158
181
  display: flex;
159
182
  flex-direction: column;
160
- flex: none;
161
- min-block-size: 0;
183
+ flex: 0 0 var(--_ui-disclosure-heading-size, var(--_trigger-min-block-size));
184
+ min-block-size: calc(var(--_ui-disclosure-heading-size, var(--_trigger-min-block-size)) + var(--ui-border-width));
185
+ transition: flex-grow var(--ui-motion-layout) var(--ui-motion-layout-ease);
162
186
  border-block-end: 0;
163
187
  border-block-start: var(--ui-border-width) solid var(--ui-border-divider);
164
188
  }
165
189
  :host-state([fill][internalOpen]) {
166
- flex: 1;
190
+ flex-grow: 1;
167
191
  }
168
192
  :host-state([fill]) .heading {
169
193
  flex: none;
@@ -172,14 +196,8 @@
172
196
  border-radius: 0;
173
197
  }
174
198
  :host-state([fill]) .panel {
175
- display: flex;
176
199
  flex: 1;
177
200
  min-block-size: 0;
178
- transition: none;
179
- }
180
- :host-state([fill]) .panel[hidden] {
181
- flex: none;
182
- block-size: 0;
183
201
  }
184
202
  :host-state([fill]) .panel-inner {
185
203
  display: flex;
@@ -189,9 +207,43 @@
189
207
  padding: 0;
190
208
  }
191
209
 
210
+ :host-state([variant="navigation"]) .trigger {
211
+ gap: var(--ui-nav-gap);
212
+ padding-inline: var(--ui-nav-padding-inline);
213
+ min-block-size: var(--ui-nav-min-block-size);
214
+ padding-block: var(--ui-nav-padding-block);
215
+ border-radius: var(--ui-selection-radius);
216
+ font-size: var(--ui-font-size-md);
217
+ font-weight: var(--ui-font-normal);
218
+ color: var(--ui-text-secondary);
219
+ }
220
+ :host-state([variant="navigation"][density="compact"]) .trigger {
221
+ padding-inline: var(--ui-nav-compact-padding-inline);
222
+ padding-block: var(--ui-nav-compact-padding-block);
223
+ min-block-size: var(--ui-nav-compact-min-block-size);
224
+ font-size: var(--ui-font-size-sm);
225
+ }
226
+ :host-state([variant="navigation"]) .trigger:hover:not(:disabled) {
227
+ background: var(--ui-nav-hover-surface);
228
+ color: var(--ui-text-primary);
229
+ }
230
+ :host-state([variant="navigation"][internalOpen]) .trigger {
231
+ color: var(--ui-selection-text);
232
+ }
233
+ :host-state([variant="navigation"]) :is(.leading, .indicator) {
234
+ inline-size: var(--ui-nav-icon-size);
235
+ font-size: var(--ui-nav-icon-size);
236
+ }
237
+ @media (pointer: coarse) {
238
+ :host-state([variant="navigation"][density="compact"]) .trigger {
239
+ min-block-size: var(--ui-control-min-block-size);
240
+ }
241
+ }
242
+
192
243
  @media (prefers-reduced-motion: reduce) {
244
+ :host-state([fill]),
193
245
  .panel,
194
- .chevron {
246
+ .indicator {
195
247
  transition: none;
196
248
  }
197
249
  }
@@ -2,6 +2,7 @@ import { trackTrigger } from "../shared/trigger.js";
2
2
 
3
3
  let disclosures = 0;
4
4
  const groups = new WeakMap();
5
+ const pendingGroups = new WeakSet();
5
6
 
6
7
  function groupFor(document, name) {
7
8
  let byName = groups.get(document);
@@ -11,6 +12,27 @@ function groupFor(document, name) {
11
12
  return members;
12
13
  }
13
14
 
15
+ function ensureOpen(members) {
16
+ if (pendingGroups.has(members)) return;
17
+ pendingGroups.add(members);
18
+ queueMicrotask(() => {
19
+ pendingGroups.delete(members);
20
+ const current = [...members];
21
+ if (!current.some(member => member.required()) || current.some(member => member.isOpen())) return;
22
+ const candidate = current.find(member => !member.disabled()) || current[0];
23
+ candidate?.open();
24
+ });
25
+ }
26
+
27
+ function leaveGroup(document, name, member) {
28
+ const byName = groups.get(document);
29
+ const members = byName?.get(name);
30
+ if (!members) return;
31
+ members.delete(member);
32
+ if (!members.size) byName.delete(name);
33
+ else ensureOpen(members);
34
+ }
35
+
14
36
  // `open` sets the disclosure initially and whenever it changes; the trigger toggles the state.
15
37
  export default function controller(host) {
16
38
  const document = host.element.ownerDocument;
@@ -19,7 +41,17 @@ export default function controller(host) {
19
41
  let external = host.props.open.value;
20
42
  host.state.internalOpen = Boolean(external);
21
43
  let groupName = "";
44
+ let disposed = false;
22
45
  const member = {
46
+ isOpen: () => Boolean(host.state.internalOpen),
47
+ required: () => Boolean(host.props.requiredOpen.value),
48
+ disabled: () => Boolean(host.props.disabled.value),
49
+ open() {
50
+ if (disposed || host.state.internalOpen) return;
51
+ host.state.internalOpen = true;
52
+ syncGroup("programmatic", "programmatic");
53
+ host.dispatch("open", { open: true, reason: "programmatic", trigger: "programmatic" });
54
+ },
23
55
  close(reason, how) {
24
56
  if (!host.state.internalOpen) return;
25
57
  host.state.internalOpen = false;
@@ -29,18 +61,24 @@ export default function controller(host) {
29
61
  const syncGroup = (reason, how) => {
30
62
  const name = String(host.props.name.value || "");
31
63
  if (name !== groupName) {
32
- if (groupName) groupFor(document, groupName).delete(member);
64
+ if (groupName) leaveGroup(document, groupName, member);
33
65
  groupName = name;
34
66
  if (groupName) groupFor(document, groupName).add(member);
35
67
  }
36
68
  if (name && host.state.internalOpen) {
37
69
  for (const peer of groupFor(document, name)) if (peer !== member) peer.close(reason, how);
38
70
  }
71
+ if (name) ensureOpen(groupFor(document, name));
72
+ };
73
+ const mayClose = () => {
74
+ if (!groupName) return true;
75
+ const members = [...groupFor(document, groupName)];
76
+ return !members.some(peer => peer.required()) || members.some(peer => peer !== member && peer.isOpen());
39
77
  };
40
78
  const stop = host.effect(() => {
41
79
  if (host.props.open.value !== external) {
42
80
  external = host.props.open.value;
43
- host.state.internalOpen = Boolean(external);
81
+ if (external || mayClose()) host.state.internalOpen = Boolean(external);
44
82
  }
45
83
  const level = Number(host.props.headingLevel.value);
46
84
  host.state.headingRole = Number.isInteger(level) && level >= 2 && level <= 6 ? "heading" : null;
@@ -52,23 +90,31 @@ export default function controller(host) {
52
90
  const onClick = () => {
53
91
  if (host.props.disabled.value) return;
54
92
  const open = !host.state.internalOpen;
93
+ if (!open && !mayClose()) return;
55
94
  const how = trigger();
56
95
  host.state.internalOpen = open;
57
96
  if (open) syncGroup("action", how);
58
97
  host.dispatch(open ? "open" : "close", { open, reason: "action", trigger: how });
59
98
  };
60
99
  const onBeforematch = () => queueMicrotask(() => {
61
- if (host.state.internalOpen) return;
100
+ if (disposed || host.state.internalOpen) return;
62
101
  host.state.internalOpen = true;
63
102
  syncGroup("programmatic", "programmatic");
64
103
  host.dispatch("open", { open: true, reason: "programmatic", trigger: "programmatic" });
65
104
  });
66
105
  host.refs.trigger.addEventListener("click", onClick);
67
106
  host.refs.panel.addEventListener("beforematch", onBeforematch);
107
+ const measureHeading = () => host.element.style.setProperty("--_ui-disclosure-heading-size", `${host.refs.heading.getBoundingClientRect().height}px`);
108
+ const ResizeObserver = document.defaultView?.ResizeObserver;
109
+ const headingObserver = ResizeObserver ? new ResizeObserver(measureHeading) : null;
110
+ headingObserver?.observe(host.refs.heading);
111
+ measureHeading();
68
112
  return () => {
113
+ disposed = true;
114
+ headingObserver?.disconnect();
69
115
  stop();
70
116
  stopTracking();
71
- if (groupName) groupFor(document, groupName).delete(member);
117
+ if (groupName) leaveGroup(document, groupName, member);
72
118
  host.refs.trigger.removeEventListener("click", onClick);
73
119
  host.refs.panel.removeEventListener("beforematch", onBeforematch);
74
120
  };
@@ -34,6 +34,7 @@
34
34
  <span class="label"><slot></slot></span>
35
35
  <span class="description"><slot name="description"></slot></span>
36
36
  </span>
37
+ <span class="trailing"><slot name="trailing"></slot></span>
37
38
  </a>
38
39
  <button $else type="button" from:aria-current="{ true: 'page', false: null, page: 'page', step: 'step', location: 'location' }[format('%s', current)]">
39
40
  <span class="indicator" aria-hidden="true"></span>
@@ -42,6 +43,7 @@
42
43
  <span class="label"><slot></slot></span>
43
44
  <span class="description"><slot name="description"></slot></span>
44
45
  </span>
46
+ <span class="trailing"><slot name="trailing"></slot></span>
45
47
  </button>
46
48
  </template>
47
49
  <style>
@@ -74,13 +76,13 @@
74
76
  box-sizing: border-box;
75
77
  display: flex;
76
78
  align-items: center;
77
- gap: var(--ui-space-3);
79
+ gap: var(--ui-nav-gap);
78
80
  inline-size: var(--ui-nav-item-inline-size, 100%);
79
81
  min-inline-size: 0;
80
- min-block-size: var(--ui-control-size, 2.5rem);
82
+ min-block-size: var(--ui-nav-min-block-size);
81
83
  margin: 0;
82
- padding-block: var(--ui-space-2);
83
- padding-inline: var(--ui-space-3);
84
+ padding-block: var(--ui-nav-padding-block);
85
+ padding-inline: var(--ui-nav-padding-inline);
84
86
  border: 0;
85
87
  border-radius: var(--ui-selection-radius);
86
88
  background: transparent;
@@ -95,7 +97,7 @@
95
97
  }
96
98
 
97
99
  :host:hover:not([aria-current]) {
98
- background: var(--ui-surface-hover);
100
+ background: var(--ui-nav-hover-surface);
99
101
  color: var(--ui-text-primary);
100
102
  }
101
103
 
@@ -144,12 +146,15 @@
144
146
  }
145
147
  }
146
148
 
147
- .leading {
149
+ .leading,
150
+ .trailing {
148
151
  display: inline-flex;
149
152
  flex: none;
150
153
  align-items: center;
154
+ justify-content: center;
155
+ inline-size: var(--ui-nav-icon-size);
151
156
  color: inherit;
152
- font-size: var(--ui-font-size-lg);
157
+ font-size: var(--ui-nav-icon-size);
153
158
  }
154
159
 
155
160
  .content {
@@ -182,11 +187,10 @@
182
187
  }
183
188
 
184
189
  :host-state([density="compact"]) {
185
- min-block-size: var(--ui-control-size-sm);
186
- padding-block: var(--ui-space-1);
187
- padding-inline: var(--ui-space-2);
190
+ min-block-size: var(--ui-nav-compact-min-block-size);
191
+ padding-block: var(--ui-nav-compact-padding-block);
192
+ padding-inline: var(--ui-nav-compact-padding-inline);
188
193
  }
189
- :host-state([density="compact"]) .leading,
190
194
  :host-state([density="compact"]) .label {
191
195
  font-size: var(--ui-font-size-sm);
192
196
  }
@@ -199,6 +203,7 @@
199
203
 
200
204
  /* An unused region takes no space. */
201
205
  .leading:empty,
206
+ .trailing:empty,
202
207
  .description:empty {
203
208
  display: none;
204
209
  }
@@ -12,6 +12,7 @@
12
12
  >Below this viewport width (40rem, 48rem, 64rem) the sidebar becomes an off-canvas drawer, closed until toggled.</prop>
13
13
  <state type="boolean" name="internalCollapsed" value="false"></state>
14
14
  <state type="boolean" name="drawer" value="false"></state>
15
+ <state type="boolean" name="resizing" value="false"></state>
15
16
  <event name="resize" type="object">
16
17
  <prop name="width" required type="number"></prop>
17
18
  <prop name="trigger" required type="keyword" values="keyboard, pointer, programmatic"></prop>
@@ -23,7 +24,7 @@
23
24
  </event>
24
25
  </defs>
25
26
  <aside>
26
- <div class="content"><slot></slot></div>
27
+ <div class="viewport"><div class="content"><slot></slot></div></div>
27
28
  <div
28
29
  class="resizer"
29
30
  $ref="resizer"
@@ -55,10 +56,15 @@
55
56
  syntax: "*";
56
57
  inherits: false;
57
58
  }
59
+ @property --ui-sidebar-collapsed-width {
60
+ syntax: "*";
61
+ inherits: false;
62
+ }
58
63
 
59
64
  :host {
60
65
  /* Hooks do not inherit, so the parts below read them from the root. */
61
66
  --_ui-sidebar-backdrop: var(--ui-sidebar-backdrop);
67
+ --_ui-sidebar-expanded-width: var(--_sidebar-width, var(--ui-sidebar-width, 280px));
62
68
 
63
69
  position: relative;
64
70
  box-sizing: border-box;
@@ -70,6 +76,7 @@
70
76
  min-block-size: 0;
71
77
  background: var(--ui-sidebar-surface, var(--ui-surface-default));
72
78
  border-inline-end: var(--ui-border-width) solid var(--ui-sidebar-border, var(--ui-border-divider));
79
+ transition: inline-size var(--ui-motion-layout) var(--ui-motion-layout-ease), visibility var(--ui-motion-layout);
73
80
  }
74
81
 
75
82
  :host-state([side="end"]) {
@@ -79,10 +86,17 @@
79
86
 
80
87
  /* The panel's content scrolls, and each edge that hides content fades: the scroll fade every
81
88
  Looma scroller uses (see ui-scroll-area). */
82
- .content {
89
+ .viewport {
83
90
  flex: 1;
84
91
  min-block-size: 0;
92
+ overflow: clip;
93
+ }
94
+ .content {
95
+ block-size: 100%;
96
+ inline-size: calc(var(--_ui-sidebar-expanded-width) - var(--ui-border-width));
97
+ min-block-size: 0;
85
98
  overflow: auto;
99
+ transition: transform var(--ui-motion-layout) var(--ui-motion-layout-ease);
86
100
  --_ui-scroll-fade-direction: to bottom;
87
101
  --_ui-scroll-fade-size: var(--ui-scroll-fade-size, 24px);
88
102
  mask-image: linear-gradient(
@@ -103,7 +117,17 @@
103
117
 
104
118
  /* Collapsed while docked. */
105
119
  :host-state([internalCollapsed]):not([popover]) {
106
- display: none;
120
+ inline-size: var(--ui-sidebar-collapsed-width, 0px);
121
+ visibility: hidden;
122
+ }
123
+ :host-state([resizing]) {
124
+ transition: none;
125
+ }
126
+ :host-state([internalCollapsed]):not([popover]) .content {
127
+ transform: translateX(-100%);
128
+ }
129
+ :host-state([side="end"][internalCollapsed]):not([popover]) .content {
130
+ transform: translateX(100%);
107
131
  }
108
132
 
109
133
  .resizer {
@@ -182,9 +206,9 @@
182
206
  box-shadow: var(--ui-elevation-overlay, var(--ui-shadow-lg));
183
207
  transform: translateX(0);
184
208
  transition:
185
- transform var(--ui-motion-base) var(--ui-motion-ease-out),
186
- display var(--ui-motion-base) allow-discrete,
187
- overlay var(--ui-motion-base) allow-discrete;
209
+ transform var(--ui-motion-reveal) var(--ui-motion-reveal-ease),
210
+ display var(--ui-motion-reveal) allow-discrete,
211
+ overlay var(--ui-motion-reveal) allow-discrete;
188
212
  }
189
213
 
190
214
  /* The author display above would otherwise override the UA rule that hides a closed popover. */
@@ -214,9 +238,14 @@
214
238
  :host[popover]::backdrop {
215
239
  background: var(--_ui-sidebar-backdrop, rgb(0 0 0 / 0.32));
216
240
  }
241
+ :host[popover] .content {
242
+ inline-size: 100%;
243
+ }
217
244
 
218
245
  @media (prefers-reduced-motion: reduce) {
219
- :host[popover] {
246
+ :host,
247
+ :host[popover],
248
+ .content {
220
249
  transition: none;
221
250
  }
222
251
  }
@@ -58,11 +58,13 @@ export default function controller(host) {
58
58
  const startX = event.clientX;
59
59
  const startWidth = currentWidth();
60
60
  const multiplier = host.props.side.value === "end" ? -1 : 1;
61
+ host.state.resizing = true;
61
62
  pointerAbort = new AbortController();
62
63
  const signal = pointerAbort.signal;
63
64
  const finish = () => {
64
65
  pointerAbort?.abort();
65
66
  pointerAbort = undefined;
67
+ host.state.resizing = false;
66
68
  };
67
69
  view.addEventListener("pointermove", (move) => {
68
70
  setWidth(startWidth + ((move.clientX - startX) * multiplier), "pointer");
@@ -75,8 +77,12 @@ export default function controller(host) {
75
77
  const endPointer = () => {
76
78
  pointerAbort?.abort();
77
79
  pointerAbort = undefined;
80
+ host.state.resizing = false;
78
81
  };
79
82
  const syncHandle = () => {
83
+ element.toggleAttribute("inert", host.state.internalCollapsed && !host.state.drawer);
84
+ if (host.state.internalCollapsed && !host.state.drawer) element.setAttribute("aria-hidden", "true");
85
+ else element.removeAttribute("aria-hidden");
80
86
  if (!host.props.resizable.value || host.state.drawer || host.state.internalCollapsed) { endPointer(); return; }
81
87
  const { min, max } = bounds();
82
88
  handle.setAttribute("aria-valuemin", String(min));