@livx.cc/bare-v3 0.1.0-alpha.46 → 0.1.0-alpha.47

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0-alpha.46",
2
+ "version": "0.1.0-alpha.47",
3
3
  "cssSha256": "4b198a96fb6998163311dad7e93b6bcbfe4e5553a3ef5d7d79c1a7dcd6e9ed39",
4
4
  "classes": [
5
5
  "b3-accordion",
@@ -295,7 +295,7 @@
295
295
  "b3-dropdown-panel",
296
296
  "b3-tree"
297
297
  ],
298
- "guidance": "Accordion and dropdown use native details/summary. Dropdown is a disclosure containing ordinary links/buttons, not an ARIA menu. mount() adds outside-click and Escape dismissal. Tree is a nested list/details presentation, not a role=tree widget. Browser disclosure behavior supplies keyboard activation.",
298
+ "guidance": "Accordion and dropdown use native details/summary. Dropdown is a disclosure containing ordinary links/buttons, not an ARIA menu. mount() adds outside-click and Escape dismissal, shifts the dropdown within horizontal viewport bounds, and opens it on the roomier side with a scrollable height when space is limited. Authored height limits are respected; close and unmount restore inline placement styles. Placement follows viewport resize and ancestor scrolling; scrolling the panel itself preserves its position. A trigger scrolled fully outside the viewport closes its disclosure without moving focus. Tree is a nested list/details presentation, not a role=tree widget. Browser disclosure behavior supplies keyboard activation.",
299
299
  "example": "<details class=\"b3-accordion\"><summary>More information</summary><p>Useful details.</p></details><details class=\"b3-dropdown\"><summary class=\"b3-button\">Actions</summary><div class=\"b3-dropdown-panel\"><a class=\"b3-nav-link\" href=\"#details\">View details</a></div></details>"
300
300
  },
301
301
  {
@@ -1,6 +1,6 @@
1
1
  # Accordion, dropdown and hierarchy
2
2
 
3
- Accordion and dropdown use native details/summary. Dropdown is a disclosure containing ordinary links/buttons, not an ARIA menu. mount() adds outside-click and Escape dismissal. Tree is a nested list/details presentation, not a role=tree widget. Browser disclosure behavior supplies keyboard activation.
3
+ Accordion and dropdown use native details/summary. Dropdown is a disclosure containing ordinary links/buttons, not an ARIA menu. mount() adds outside-click and Escape dismissal, shifts the dropdown within horizontal viewport bounds, and opens it on the roomier side with a scrollable height when space is limited. Authored height limits are respected; close and unmount restore inline placement styles. Placement follows viewport resize and ancestor scrolling; scrolling the panel itself preserves its position. A trigger scrolled fully outside the viewport closes its disclosure without moving focus. Tree is a nested list/details presentation, not a role=tree widget. Browser disclosure behavior supplies keyboard activation.
4
4
 
5
5
  Classes: b3-accordion, b3-dropdown, b3-dropdown-panel, b3-tree
6
6
 
package/dist/ui.js CHANGED
@@ -305,18 +305,55 @@ function reel({ root, document: document2, find, options }) {
305
305
 
306
306
  // src/interactions/disclosures.js
307
307
  function disclosures({ root, document: document2, options }) {
308
+ const placements = new Map;
309
+ const properties = ["transform", "inset-block-start", "inset-block-end", "max-block-size"];
310
+ function restore(panel) {
311
+ const original = placements.get(panel);
312
+ if (!original)
313
+ return;
314
+ for (const [property, value, priority] of original) {
315
+ if (value)
316
+ panel.style.setProperty(property, value, priority);
317
+ else
318
+ panel.style.removeProperty(property);
319
+ }
320
+ }
321
+ options.signal.addEventListener("abort", () => {
322
+ for (const panel of placements.keys())
323
+ restore(panel);
324
+ placements.clear();
325
+ }, { once: true });
308
326
  function placeDisclosure(disclosure) {
309
327
  const panel = disclosure.querySelector(".b3-dropdown-panel");
310
- if (!panel || !disclosure.open)
328
+ if (!panel)
329
+ return;
330
+ restore(panel);
331
+ if (!disclosure.open) {
332
+ placements.delete(panel);
311
333
  return;
312
- panel.style.transform = "";
313
- panel.style.insetBlockStart = "";
314
- panel.style.insetBlockEnd = "";
334
+ }
335
+ if (!placements.has(panel)) {
336
+ placements.set(panel, properties.map((property) => [
337
+ property,
338
+ panel.style.getPropertyValue(property),
339
+ panel.style.getPropertyPriority(property)
340
+ ]));
341
+ }
315
342
  const viewport = document2.defaultView;
316
343
  const bounds = panel.getBoundingClientRect();
317
344
  const shift = bounds.left < 8 ? 8 - bounds.left : bounds.right > viewport.innerWidth - 8 ? viewport.innerWidth - 8 - bounds.right : 0;
318
345
  panel.style.transform = `translateX(${shift}px)`;
319
- if (bounds.bottom > viewport.innerHeight - 8 && disclosure.getBoundingClientRect().top > bounds.height + 8) {
346
+ const trigger = disclosure.getBoundingClientRect();
347
+ if (trigger.bottom <= 0 || trigger.top >= viewport.innerHeight) {
348
+ disclosure.open = false;
349
+ return;
350
+ }
351
+ const gap = Math.max(0, bounds.top - trigger.bottom);
352
+ const below = Math.max(0, viewport.innerHeight - trigger.bottom - gap - 8);
353
+ const above = Math.max(0, trigger.top - gap - 8);
354
+ const opensAbove = bounds.height > below && above > below;
355
+ panel.style.maxBlockSize = `${Math.min(bounds.height, opensAbove ? above : below)}px`;
356
+ if (opensAbove) {
320
357
  panel.style.insetBlockStart = "auto";
321
358
  panel.style.insetBlockEnd = "calc(100% + .5rem)";
322
359
  }
@@ -328,6 +365,14 @@ function disclosures({ root, document: document2, options }) {
328
365
  document2.defaultView.addEventListener("resize", () => {
329
366
  root.querySelectorAll("details.b3-dropdown[open]").forEach(placeDisclosure);
330
367
  }, options);
368
+ document2.defaultView.addEventListener("scroll", (event) => {
369
+ for (const disclosure of root.querySelectorAll("details.b3-dropdown[open]")) {
370
+ const panel = disclosure.querySelector(".b3-dropdown-panel");
371
+ if (event.target?.nodeType && panel?.contains(event.target))
372
+ continue;
373
+ placeDisclosure(disclosure);
374
+ }
375
+ }, { ...options, capture: true, passive: true });
331
376
  root.addEventListener("keydown", (event) => {
332
377
  if (event.key === "Escape") {
333
378
  const disclosure = event.target.closest?.("details.b3-dropdown[open]");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/bare-v3",
3
- "version": "0.1.0-alpha.46",
3
+ "version": "0.1.0-alpha.47",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",