Skip to content

chore(release): version packages - #51

Open
github-actions[bot] wants to merge 1 commit into
mainfrom
changeset-release/main
Open

chore(release): version packages#51
github-actions[bot] wants to merge 1 commit into
mainfrom
changeset-release/main

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@dunky.dev/dom-dialog@0.3.0

Minor Changes

  • #53 2cc4a1b Thanks @ivanbanov! - Dialog.Content gains restoreFocus — the close-side counterpart to
    initialFocus. Closing still returns focus to whatever held it before the
    dialog opened; restoreFocus names where it goes when that holder can't
    meaningfully take focus back: focus sat on the body (a pointer press can
    leave it there), or on an element removed from the document since.
    Typically the dialog's trigger.

    // React — a ref, read at close time
    <Dialog.Content restoreFocus={triggerRef}></Dialog.Content>
    
    // Solid — an element or accessor, resolved at close time
    <Dialog.Content restoreFocus={() => trigger}></Dialog.Content>

    Before, those two cases silently dropped focus: restoring to the body goes
    nowhere, and focusing a disconnected element is a no-op, leaving focus
    stranded on the closing layer. The element focused before opening still
    always wins when it is meaningful — the fallback never overrides it. At the
    DOM layer, openDialogLayer takes restoreFocus?: () => HTMLElement | null,
    resolved at close.

Patch Changes

  • #50 bbb04da Thanks @ivanbanov! - Focus candidates barred by an ancestor are excluded: controls disabled through
    fieldset[disabled] and anything inside [inert].

    FOCUSABLE_SELECTOR and the form-field selector gate on an element's own
    attributes (input:not([disabled])), but both bars also arrive from
    ancestors, so a barred control satisfied the selector while a browser refuses
    to focus it — silently.

    In the focus trap that was a hard dead end: the Tab keydown is already
    preventDefault()-ed when focus is stepped by hand, so every press recomputed
    the same refused target and focus never moved again. The cycle now only holds
    what a browser would actually focus, keeping the native exception that
    controls in a disabled fieldset's first legend stay enabled.

    In the initial-focus chain it was the quieter failure mode: the barred field
    won the draw, focus() no-opped, and focus fell to the overlay window even
    when a viable field came later. Every candidate — designated element and form
    fields alike — is now also filtered for these bars.

    Both use the new isFocusable from @dunky.dev/dom-element, beside the
    isRendered filter they already shared.

  • #50 bfbe863 Thanks @ivanbanov! - Initial focus now skips a candidate that didn't render.

    getInitialFocus filtered [disabled] and [type="hidden"] but never asked
    whether the element actually rendered. A field inside a collapsed section
    satisfied the selector and won the draw; focus() on it did nothing — and said
    nothing — so focus fell back to the dialog window, with the fallback's warning
    unable to fire, because from its point of view the fallback had succeeded. The
    overlay opened on its window instead of the field: degraded, not broken, and
    silent.

    A designated initialFocus that hadn't rendered was worse. It went straight to
    the window and skipped the form-field step entirely, contradicting the
    documented "when one is set and can take focus". So getInitialFocus now
    takes the designated element as a second argument and resolves the whole chain
    in one call, filtering every step rather than just the last:

    // designated -> first form field -> the overlay window itself
    getInitialFocus(content, designatedElement).focus({ preventScroll: true })

    Callers that were writing initialFocus ?? getInitialFocus(content) should
    pass the designated element in instead — the ?? is what spent it on a
    candidate that couldn't take focus. @dunky.dev/dom-dialog does this for every
    DOM substrate already, so a dialog's initialFocus inherits the fix without a
    change on the consumer's side.

    The predicate is isRendered from @dunky.dev/dom-element, shared with the
    focus trap so the two can't disagree on what counts as rendered.

  • Updated dependencies [bbb04da, bfbe863, f5becd4, bfbe863, 772a7df]:

    • @dunky.dev/dom-focus-trap@0.1.3
    • @dunky.dev/dom-overlay@0.2.1
    • @dunky.dev/browser-navigation@0.2.1

@dunky.dev/dom-element@0.1.0

Minor Changes

  • #50 6c249f9 Thanks @ivanbanov! - New package: @dunky.dev/dom-element, framework-free predicates about a single
    element.

    isRendered(element) answers whether an element actually rendered, and so can
    take focus, be pressed, or be read out. Presence in the DOM is not enough: an
    element inside a collapsed section still answers querySelector, but focus()
    on it does nothing and reports nothing.

    import { isRendered } from '@dunky.dev/dom-element'
    
    for (const field of content.querySelectorAll('input, select, textarea')) {
      if (isRendered(field)) {
        field.focus()
        break
      }
    }

    Checked: the hidden attribute (hidden="until-found" included),
    display: none on the element or any ancestor — display doesn't inherit, so
    ancestors are walked — visibility: hidden | collapse, and being detached.
    Not checked: opacity: 0 and content-visibility, which do render, and
    rendering is what decides focusability.

    isFocusable(element) is the sibling facet: whether anything bars the element
    from taking focus. Disabling and inertness also arrive from ancestors — a
    control inside a fieldset[disabled] subtree (with the native exception for
    its first legend) or anything inside [inert] refuses focus() — which a
    selector's own-attribute checks (:not([disabled])) can't see. The facets are
    deliberately narrow and compose; the tab order stays the caller's question.

    It's a package of its own because two utils have to agree on the answers:
    @dunky.dev/dom-focus-trap filters its Tab cycle with them and
    @dunky.dev/dom-overlay filters its initial-focus candidates, both guarding
    against the same silent focus() no-op.

@dunky.dev/react-dialog@0.5.0

Minor Changes

  • #53 2cc4a1b Thanks @ivanbanov! - Dialog.Content gains restoreFocus — the close-side counterpart to
    initialFocus. Closing still returns focus to whatever held it before the
    dialog opened; restoreFocus names where it goes when that holder can't
    meaningfully take focus back: focus sat on the body (a pointer press can
    leave it there), or on an element removed from the document since.
    Typically the dialog's trigger.

    // React — a ref, read at close time
    <Dialog.Content restoreFocus={triggerRef}></Dialog.Content>
    
    // Solid — an element or accessor, resolved at close time
    <Dialog.Content restoreFocus={() => trigger}></Dialog.Content>

    Before, those two cases silently dropped focus: restoring to the body goes
    nowhere, and focusing a disconnected element is a no-op, leaving focus
    stranded on the closing layer. The element focused before opening still
    always wins when it is meaningful — the fallback never overrides it. At the
    DOM layer, openDialogLayer takes restoreFocus?: () => HTMLElement | null,
    resolved at close.

Patch Changes

  • Updated dependencies [2cc4a1b, bbb04da, bfbe863]:
    • @dunky.dev/dom-dialog@0.3.0
    • @dunky.dev/react-use-focus-trap@0.1.3

@dunky.dev/solid-dialog@0.3.0

Minor Changes

  • #53 2cc4a1b Thanks @ivanbanov! - Dialog.Content gains restoreFocus — the close-side counterpart to
    initialFocus. Closing still returns focus to whatever held it before the
    dialog opened; restoreFocus names where it goes when that holder can't
    meaningfully take focus back: focus sat on the body (a pointer press can
    leave it there), or on an element removed from the document since.
    Typically the dialog's trigger.

    // React — a ref, read at close time
    <Dialog.Content restoreFocus={triggerRef}></Dialog.Content>
    
    // Solid — an element or accessor, resolved at close time
    <Dialog.Content restoreFocus={() => trigger}></Dialog.Content>

    Before, those two cases silently dropped focus: restoring to the body goes
    nowhere, and focusing a disconnected element is a no-op, leaving focus
    stranded on the closing layer. The element focused before opening still
    always wins when it is meaningful — the fallback never overrides it. At the
    DOM layer, openDialogLayer takes restoreFocus?: () => HTMLElement | null,
    resolved at close.

Patch Changes

  • Updated dependencies [2cc4a1b, bbb04da, bfbe863]:
    • @dunky.dev/dom-dialog@0.3.0
    • @dunky.dev/solid-use-focus-trap@0.1.2

@dunky.dev/dom-focus-trap@0.1.3

Patch Changes

  • #50 bbb04da Thanks @ivanbanov! - Focus candidates barred by an ancestor are excluded: controls disabled through
    fieldset[disabled] and anything inside [inert].

    FOCUSABLE_SELECTOR and the form-field selector gate on an element's own
    attributes (input:not([disabled])), but both bars also arrive from
    ancestors, so a barred control satisfied the selector while a browser refuses
    to focus it — silently.

    In the focus trap that was a hard dead end: the Tab keydown is already
    preventDefault()-ed when focus is stepped by hand, so every press recomputed
    the same refused target and focus never moved again. The cycle now only holds
    what a browser would actually focus, keeping the native exception that
    controls in a disabled fieldset's first legend stay enabled.

    In the initial-focus chain it was the quieter failure mode: the barred field
    won the draw, focus() no-opped, and focus fell to the overlay window even
    when a viable field came later. Every candidate — designated element and form
    fields alike — is now also filtered for these bars.

    Both use the new isFocusable from @dunky.dev/dom-element, beside the
    isRendered filter they already shared.

  • #50 bfbe863 Thanks @ivanbanov! - The Tab cycle's rendered check now comes from @dunky.dev/dom-element instead
    of a private copy.

    @dunky.dev/dom-overlay needs the same predicate to filter its initial-focus
    candidates, and two packages answering the question separately would drift.
    The check itself is unchanged in intent — a non-rendered element is a no-op to
    focus, so keeping one in the cycle would stall the trap on it — but sharing it
    tightens two cases:

    • A detached element is now excluded. It can't take focus, and computed
      style on one reports the property defaults rather than none, so the display
      walk alone let it through.
    • A display: none ancestor above the container now excludes the
      focusables under it. The private copy stopped its walk at the container.
      Nothing inside a hidden container can take focus either way, so this lands on
      the trap's documented behavior for an empty cycle: Tab is a no-op.
  • Updated dependencies [6c249f9]:

    • @dunky.dev/dom-element@0.1.0

@dunky.dev/browser-navigation@0.2.1

Patch Changes

  • #52 f5becd4 Thanks @ivanbanov! - SPEC.md (shipped with the package) gains a Scenarios section: 26 compact
    traces of interceptBackNavigation and watchSpentEntry behavior, grouped
    by one layer, release and consumption, forward/claims/reload, stacked
    layers, and timing edges.

    The behavior contract was already fully stated, but as one mechanism per
    prose bullet — nothing let a reader replay a concrete flow end to end.
    Each trace is a replayable episode in the module's own vocabulary (arm,
    release, Back, Forward), e.g.:

    arm A -> arm B -> arm C -> Back -> release B -> Forward -> Forward
      => the Back closes C; the first Forward soaks into B's abandoned entry;
         the second asks C, which reopens only if it still can without B.
    

    Two outcomes the prose previously left implicit are now stated outright: a
    deliberately released entry absorbs one Forward press (it can't be deleted,
    only left to soak the traversal), and any re-plant — a new layer or a
    veto's re-arm — truncates every parked Forward watch above it.

@dunky.dev/dom-overlay@0.2.1

Patch Changes

  • #50 bbb04da Thanks @ivanbanov! - Focus candidates barred by an ancestor are excluded: controls disabled through
    fieldset[disabled] and anything inside [inert].

    FOCUSABLE_SELECTOR and the form-field selector gate on an element's own
    attributes (input:not([disabled])), but both bars also arrive from
    ancestors, so a barred control satisfied the selector while a browser refuses
    to focus it — silently.

    In the focus trap that was a hard dead end: the Tab keydown is already
    preventDefault()-ed when focus is stepped by hand, so every press recomputed
    the same refused target and focus never moved again. The cycle now only holds
    what a browser would actually focus, keeping the native exception that
    controls in a disabled fieldset's first legend stay enabled.

    In the initial-focus chain it was the quieter failure mode: the barred field
    won the draw, focus() no-opped, and focus fell to the overlay window even
    when a viable field came later. Every candidate — designated element and form
    fields alike — is now also filtered for these bars.

    Both use the new isFocusable from @dunky.dev/dom-element, beside the
    isRendered filter they already shared.

  • #50 bfbe863 Thanks @ivanbanov! - Initial focus now skips a candidate that didn't render.

    getInitialFocus filtered [disabled] and [type="hidden"] but never asked
    whether the element actually rendered. A field inside a collapsed section
    satisfied the selector and won the draw; focus() on it did nothing — and said
    nothing — so focus fell back to the dialog window, with the fallback's warning
    unable to fire, because from its point of view the fallback had succeeded. The
    overlay opened on its window instead of the field: degraded, not broken, and
    silent.

    A designated initialFocus that hadn't rendered was worse. It went straight to
    the window and skipped the form-field step entirely, contradicting the
    documented "when one is set and can take focus". So getInitialFocus now
    takes the designated element as a second argument and resolves the whole chain
    in one call, filtering every step rather than just the last:

    // designated -> first form field -> the overlay window itself
    getInitialFocus(content, designatedElement).focus({ preventScroll: true })

    Callers that were writing initialFocus ?? getInitialFocus(content) should
    pass the designated element in instead — the ?? is what spent it on a
    candidate that couldn't take focus. @dunky.dev/dom-dialog does this for every
    DOM substrate already, so a dialog's initialFocus inherits the fix without a
    change on the consumer's side.

    The predicate is isRendered from @dunky.dev/dom-element, shared with the
    focus trap so the two can't disagree on what counts as rendered.

  • #49 772a7df Thanks @ivanbanov! - Page content beside a layer portalled into an app branch is now hidden by a
    modal layer's containment.

    Containment holds a few elements out of the hiding — the topmost modal layer,
    its backdrop, and the layers stacked above it — and it matched them by
    ancestry, so a branch that contained one was skipped whole. Where a layer
    sits is the consumer's choice: container on the Portal part lets it land
    anywhere, and when that branch also held page content, the entire branch went
    unhidden — the page reachable by pointer, keyboard, and screen reader for as
    long as the layer was open.

    // The menu lands inside the app branch, beside the page content.
    <Dialog.Portal container={appElement}>

    Hiding now descends from the body instead of walking up from the layer. A
    branch that holds one of those retained elements is descended into rather than
    spared, so the content beside it is hidden individually while the layer itself
    stays reachable. A layer at or above the body is a no-op — nothing sits
    outside it.

  • Updated dependencies [6c249f9]:

    • @dunky.dev/dom-element@0.1.0

@dunky.dev/react-use-focus-trap@0.1.3

Patch Changes

  • Updated dependencies [bbb04da, bfbe863]:
    • @dunky.dev/dom-focus-trap@0.1.3

@dunky.dev/solid-use-focus-trap@0.1.2

Patch Changes

  • Updated dependencies [bbb04da, bfbe863]:
    • @dunky.dev/dom-focus-trap@0.1.3

@github-actions
github-actions Bot force-pushed the changeset-release/main branch from 181e716 to d6f33a0 Compare August 26, 2026 16:43
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from d6f33a0 to 31a2af2 Compare August 27, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants