@jsenv/dom 0.14.4 → 0.14.6

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.
Files changed (2) hide show
  1. package/dist/jsenv_dom.js +57 -25
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -420,7 +420,7 @@ const getEventLabel = (e) => {
420
420
  return e.type;
421
421
  }
422
422
  if (e.type === "keydown") {
423
- const key = e.key === " " ? "space" : e.key.toLowerCase();
423
+ const key = e.key === " " ? "space" : e.key?.toLowerCase();
424
424
  const modifiers = [];
425
425
  if (e.ctrlKey) {
426
426
  modifiers.push("ctrl");
@@ -507,6 +507,15 @@ const createIterableWeakSet = () => {
507
507
  };
508
508
  };
509
509
 
510
+ /**
511
+ * Creates a simple publish/subscribe pair.
512
+ *
513
+ * @param {boolean} [clearOnPublish=false] - When true, all subscribers are removed after each publish call.
514
+ * @returns {[publish: (...args: any[]) => any[], subscribe: (callback: Function) => () => void, clear: () => void]}
515
+ * - `publish(...args)` — calls all subscribers with the given arguments and returns their return values.
516
+ * - `subscribe(callback)` — registers a subscriber and returns an unsubscribe function.
517
+ * - `clear()` — removes all subscribers without calling them.
518
+ */
510
519
  const createPubSub = (clearOnPublish = false) => {
511
520
  const callbackSet = new Set();
512
521
 
@@ -4593,7 +4602,13 @@ const DEFAULT_BEHAVIORS = [
4593
4602
  {
4594
4603
  test: (el) => el.matches("input[type='radio'], input[type='checkbox']"),
4595
4604
  keys: {
4596
- space: "activate",
4605
+ space: (e) => {
4606
+ if (e.target.type === "radio" && e.target.checked) {
4607
+ // space on checked radio does nothing
4608
+ return "";
4609
+ }
4610
+ return "activate";
4611
+ },
4597
4612
  enter: (e) => (e.target.form ? "form_submit" : ""),
4598
4613
  arrowleft: "focus_nav",
4599
4614
  arrowright: "focus_nav",
@@ -4609,17 +4624,20 @@ const DEFAULT_BEHAVIORS = [
4609
4624
  keys: {
4610
4625
  escape: (e) => {
4611
4626
  if (e.target.type === "search") {
4627
+ if (e.target.readOnly) {
4628
+ return "";
4629
+ }
4612
4630
  return e.target.value ? "clear" : "";
4613
4631
  }
4614
4632
  return "";
4615
4633
  },
4616
4634
  enter: (e) => (e.target.form ? "form_submit" : ""),
4617
- arrowleft: "cursor_move",
4618
- arrowright: "cursor_move",
4619
- arrowup: "cursor_move",
4620
- arrowdown: "cursor_move",
4621
- home: "cursor_move",
4622
- end: "cursor_move",
4635
+ arrowleft: (e) => (e.target.readOnly ? "scroll" : "cursor_move"),
4636
+ arrowright: (e) => (e.target.readOnly ? "scroll" : "cursor_move"),
4637
+ arrowup: (e) => (e.target.readOnly ? "scroll" : "cursor_move"),
4638
+ arrowdown: (e) => (e.target.readOnly ? "scroll" : "cursor_move"),
4639
+ home: (e) => (e.target.readOnly ? "scroll" : "cursor_move"),
4640
+ end: (e) => (e.target.readOnly ? "scroll" : "cursor_move"),
4623
4641
  },
4624
4642
  fallback: (e) => (isTypingIntent(e) ? "type" : undefined),
4625
4643
  },
@@ -4658,7 +4676,7 @@ const DEFAULT_BEHAVIORS = [
4658
4676
  ),
4659
4677
  keys: {
4660
4678
  space: "activate",
4661
- enter: (e) => (e.target.form ? "form_submit" : ""),
4679
+ enter: "activate",
4662
4680
  arrowleft: "value_change",
4663
4681
  arrowright: "value_change",
4664
4682
  arrowup: "value_change",
@@ -4666,19 +4684,19 @@ const DEFAULT_BEHAVIORS = [
4666
4684
  },
4667
4685
  },
4668
4686
  {
4669
- // Color input: Space opens the color picker, Enter submits the form
4687
+ // Color input: Space opens the color picker, Enter too
4670
4688
  test: (el) => el.matches("input[type='color']"),
4671
4689
  keys: {
4672
4690
  space: "activate",
4673
- enter: (e) => (e.target.form ? "form_submit" : ""),
4691
+ enter: "activate",
4674
4692
  },
4675
4693
  },
4676
4694
  {
4677
- // File input: Space opens the picker, Enter submits the form
4695
+ // File input: Space opens the picker, Enter too
4678
4696
  test: (el) => el.matches("input[type='file']"),
4679
4697
  keys: {
4680
4698
  space: "activate",
4681
- enter: (e) => (e.target.form ? "form_submit" : ""),
4699
+ enter: "activate",
4682
4700
  },
4683
4701
  },
4684
4702
  {
@@ -4725,7 +4743,10 @@ const DEFAULT_BEHAVIORS = [
4725
4743
  {
4726
4744
  // SELECT: don't intercept anything while the dropdown may be open
4727
4745
  test: (el) => el.tagName === "SELECT",
4728
- keys: {},
4746
+ keys: {
4747
+ space: "activate",
4748
+ enter: "activate",
4749
+ },
4729
4750
  },
4730
4751
  {
4731
4752
  // Non-interactive elements: browser scrolls on Space and arrow keys
@@ -11328,9 +11349,11 @@ const visibleRectEffect = (
11328
11349
  }
11329
11350
  }
11330
11351
  {
11331
- // visualViewport resize is a superset of window resize:
11332
- // it also fires when the virtual keyboard opens/closes on mobile.
11333
- // Fall back to window resize when visualViewport is unavailable.
11352
+ // visualViewport resize fires when the virtual keyboard opens/closes on mobile.
11353
+ // window resize catches cases visualViewport misses, notably the browser input
11354
+ // accessory bar (password/autocomplete suggestions) on iOS Safari which changes
11355
+ // the available height without always firing a visualViewport resize event.
11356
+ // We listen to both simultaneously; the check is idempotent so duplicates are harmless.
11334
11357
  const onResize = (e) => {
11335
11358
  autoCheck(e);
11336
11359
  };
@@ -11339,12 +11362,11 @@ const visibleRectEffect = (
11339
11362
  addTeardown(() => {
11340
11363
  window.visualViewport.removeEventListener("resize", onResize);
11341
11364
  });
11342
- } else {
11343
- window.addEventListener("resize", onResize);
11344
- addTeardown(() => {
11345
- window.removeEventListener("resize", onResize);
11346
- });
11347
11365
  }
11366
+ window.addEventListener("resize", onResize);
11367
+ addTeardown(() => {
11368
+ window.removeEventListener("resize", onResize);
11369
+ });
11348
11370
  }
11349
11371
  {
11350
11372
  // visualViewport scroll fires when the visual viewport pans independently
@@ -11556,9 +11578,19 @@ const visibleRectEffect = (
11556
11578
  * @param {HTMLElement} anchor - The anchor element to position against
11557
11579
  * @param {object} [options]
11558
11580
  * @param {string} [options.positionX="center"] - Preferred X placement, with viewport fallback.
11581
+ * "to-the-left" — element.right = anchor.left (sits entirely to the left of anchor)
11582
+ * "left-aligned" — element.left = anchor.left (left edges aligned)
11583
+ * "center" — element centered horizontally over anchor (default)
11584
+ * "right-aligned" — element.right = anchor.right (right edges aligned)
11585
+ * "to-the-right" — element.left = anchor.right (sits entirely to the right of anchor)
11559
11586
  * @param {string} [options.positionY="below"] - Preferred Y placement, with viewport fallback.
11560
- * @param {string} [options.positionXFixed] - Force X placement, skipping the fit-check.
11561
- * @param {string} [options.positionYFixed] - Force Y placement, skipping the fit-check.
11587
+ * "above" — element.bottom = anchor.top (sits above, no overlap)
11588
+ * "above-overlap" element.bottom = anchor.bottom (sits above, overlapping anchor)
11589
+ * "center" — element centered vertically over anchor
11590
+ * "below-overlap" — element.top = anchor.top (sits below, overlapping anchor)
11591
+ * "below" — element.top = anchor.bottom (sits below, no overlap) (default)
11592
+ * @param {string} [options.positionXFixed] - Force X placement, skipping the fit-check. Same values as positionX.
11593
+ * @param {string} [options.positionYFixed] - Force Y placement, skipping the fit-check. Same values as positionY.
11562
11594
  * @param {number} [options.alignToViewportEdgeWhenAnchorNearEdge=0] - Snap to viewport left
11563
11595
  * edge when anchor is within this many px of the left edge and element is wider than anchor.
11564
11596
  * @param {number} [options.minLeft=0] - Minimum left coordinate (document-relative).
@@ -15146,4 +15178,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
15146
15178
  };
15147
15179
  };
15148
15180
 
15149
- export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, captureScrollState, chainEvent, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findEvent, findFocusDelegateTarget, findFocusable, formatEventSideEffect, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getKeyboardEventDefaultAction, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isSameColor, isScrollable, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, parseStyle, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToReorder, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
15181
+ export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, captureScrollState, chainEvent, contrastColor, createBackgroundColorTransition, createBackgroundTransition, createBorderRadiusTransition, createBorderTransition, createDragGestureController, createDragToMoveGestureController, createEventGroupLogger, createGroupTransitionController, createHeightTransition, createIterableWeakSet, createOpacityTransition, createPubSub, createStyleController, createTimelineTransition, createTransition, createTranslateXTransition, createValueEffect, createWidthTransition, cubicBezier, dispatchCustomEvent, dispatchInternalCustomEvent, dispatchPublicCustomEvent, dragAfterThreshold, elementIsFocusable, elementIsVisibleForFocus, elementIsVisuallyVisible, findAfter, findAncestor, findBefore, findDescendant, findEvent, findFocusDelegateTarget, findFocusable, formatEventSideEffect, getAvailableHeight, getAvailableWidth, getBackground, getBackgroundColor, getBorder, getBorderRadius, getBorderSizes, getContrastRatio, getDefaultStyles, getDragCoordinates, getDropTargetInfo, getElementSignature, getFirstVisuallyVisibleAncestor, getFocusVisibilityInfo, getHeight, getHeightWithoutTransition, getInnerHeight, getInnerWidth, getKeyboardEventDefaultAction, getLuminance, getMarginSizes, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getOpacity, getOpacityWithoutTransition, getPaddingSizes, getPositionedParent, getPreferedColorScheme, getScrollBox, getScrollContainer, getScrollContainerSet, getScrollRelativeRect, getSelfAndAncestorScrolls, getStyle, getTranslateX, getTranslateXWithoutTransition, getTranslateY, getVisuallyVisibleInfo, getWidth, getWidthWithoutTransition, hasCSSSizeUnit, initFlexDetailsSet, initFocusGroup, initPositionSticky, isSameColor, isScrollable, measureLongestVisualLineWidth, measureScrollbar, measureWidestChildRow, mergeOneStyle, mergeTwoStyles, normalizeKeyboardKey, normalizeStyle, normalizeStyles, parseStyle, performTabNavigation, pickPositionRelativeTo, prefersDarkColors, prefersLightColors, preventFocusNav, preventFocusNavViaKeyboard, preventIntermediateScrollbar, resolveCSSColor, resolveCSSSize, resolveColorLuminance, resolveOklchLightness, scrollIntoViewScoped, scrollIntoViewWithStickyAwareness, setAttribute, setAttributes, setStyles, snapToPixel, startDragToReorder, startDragToResizeGesture, stickyAsRelativeCoords, stringifyStyle, trapFocusInside, trapScrollInside, useActiveElement, useAvailableHeight, useAvailableWidth, useMaxHeight, useMaxWidth, useResizeStatus, visibleRectEffect };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.14.4",
3
+ "version": "0.14.6",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {