@jsenv/dom 0.14.2 → 0.14.3

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 +72 -46
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -208,9 +208,10 @@ const dispatchInternalCustomEvent = (
208
208
  customEventDetail,
209
209
  ) => {
210
210
  const customEvent = new CustomEvent(customEventName, {
211
- detail: resolveEventDetail(customEventDetail),
211
+ detail: customEventDetail,
212
212
  cancelable: true,
213
213
  });
214
+ chainEvent(customEvent, customEventDetail?.event);
214
215
  return el.dispatchEvent(customEvent);
215
216
  };
216
217
 
@@ -224,10 +225,11 @@ const dispatchPublicCustomEvent = (
224
225
  customEventDetail,
225
226
  ) => {
226
227
  const customEvent = new CustomEvent(customEventName, {
227
- detail: resolveEventDetail(customEventDetail),
228
+ detail: customEventDetail,
228
229
  bubbles: true,
229
230
  cancelable: true,
230
231
  });
232
+ chainEvent(customEvent, customEventDetail?.event);
231
233
  return el.dispatchEvent(customEvent);
232
234
  };
233
235
 
@@ -239,26 +241,28 @@ const dispatchPublicCustomEvent = (
239
241
  */
240
242
  const dispatchCustomEvent = (el, customEventName, customEventDetail) => {
241
243
  const customEvent = new CustomEvent(customEventName, {
242
- detail: resolveEventDetail(customEventDetail),
244
+ detail: customEventDetail,
243
245
  cancelable: true,
244
246
  });
247
+ chainEvent(customEvent, customEventDetail?.event);
245
248
  const result = el.dispatchEvent(customEvent);
246
249
  return result;
247
250
  };
248
251
 
249
- const resolveEventDetail = (customEventDetail) => {
250
- const { event, ...rest } = customEventDetail ?? {};
251
- const isWrappedCustomEvent = event?.detail?.event !== undefined;
252
- if (!isWrappedCustomEvent) {
253
- return { ...rest, event };
252
+ const chainEvent = (customEvent, parentEvent) => {
253
+ if (!parentEvent) {
254
+ return customEvent;
254
255
  }
255
- // Keep `event` as the direct parent so callers see the immediate facade.
256
- // Build eventChain as [root, ...grandparents] — oldest first, excluding `event`.
257
- const previousChain = event.detail.eventChain;
256
+ // Always build eventChain from the first wrapping so callers can rely on it
257
+ // being present whenever `parentEvent` is set.
258
+ // eventChain = [oldest, ..., parentEvent] — the full ancestor list including the direct parent.
259
+ const previousChain = parentEvent.detail?.eventChain;
258
260
  const eventChain = previousChain
259
- ? [...previousChain, event.detail.event]
260
- : [event.detail.event];
261
- return { ...rest, event, eventChain };
261
+ ? [...previousChain, parentEvent]
262
+ : [parentEvent];
263
+ customEvent.detail.event = parentEvent;
264
+ customEvent.detail.eventChain = eventChain;
265
+ return customEvent;
262
266
  };
263
267
 
264
268
  /**
@@ -288,12 +292,6 @@ const findEvent = (event, predicate) => {
288
292
  }
289
293
  }
290
294
  }
291
- const initiator = event.detail?.event;
292
- if (initiator) {
293
- if (match(initiator)) {
294
- return initiator;
295
- }
296
- }
297
295
  return undefined;
298
296
  };
299
297
 
@@ -314,17 +312,15 @@ const resolveEventPredicate = (predicate) => {
314
312
  */
315
313
  const formatEventSideEffect = (e, sideEffect) => {
316
314
  const parts = [];
317
- if (e.detail?.event !== undefined) {
315
+ if (e.detail?.eventChain) {
318
316
  const chain = e.detail.eventChain;
319
- const initiator = chain ? chain[0] : e.detail.event;
317
+ const initiator = chain[0];
320
318
  parts.push(
321
319
  `"${initiator.type}" on ${getElementSignature(initiator.target)}`,
322
320
  );
323
- if (chain) {
324
- for (const chainedEvent of chain.slice(1)) {
325
- parts.push(chainedEvent.type);
326
- }
327
- parts.push(e.detail.event.type);
321
+ // chain[0] is shown as initiator above; chain includes event as last element
322
+ for (const chainedEvent of chain.slice(1)) {
323
+ parts.push(chainedEvent.type);
328
324
  }
329
325
  parts.push(e.type);
330
326
  } else {
@@ -335,11 +331,12 @@ const formatEventSideEffect = (e, sideEffect) => {
335
331
 
336
332
  /**
337
333
  * Creates a stateful debug logger that groups side effects by their native initiator event.
334
+ * Use createCategory(name, color) to get a typed logger function for each concern.
338
335
  *
339
336
  * Usage:
340
- * const log = createEventGroupLogger();
341
- * log(e, "navi_action_requested"); // opens/reuses a group for the initiator event
342
- * log("plain message"); // logs inside the current group (or standalone)
337
+ * const logger = createEventGroupLogger();
338
+ * const logAction = logger.createCategory("[action]", "#e67e22");
339
+ * logAction(e, "action started"); // opens/reuses a group for the initiator event
343
340
  *
344
341
  * The group closes automatically after the current JS task completes (setTimeout 0).
345
342
  */
@@ -358,14 +355,18 @@ const createEventGroupLogger = () => {
358
355
  }, 0);
359
356
  };
360
357
 
361
- return (eOrMessage, sideEffect, ...args) => {
362
- if (!(eOrMessage instanceof Event)) {
363
- console.debug(eOrMessage);
358
+ const log = (category, color, e, ...args) => {
359
+ if (!(e instanceof Event)) {
360
+ console.debug(
361
+ `%c${category}`,
362
+ `color:${color};font-weight:bold`,
363
+ e,
364
+ ...args,
365
+ );
364
366
  return;
365
367
  }
366
- const e = eOrMessage;
367
368
  const chain = e.detail?.eventChain;
368
- const initiator = chain ? chain[0] : (e.detail?.event ?? e);
369
+ const initiator = chain ? chain[0] : e;
369
370
  if (initiator !== currentInitiator) {
370
371
  if (currentInitiator !== null) {
371
372
  clearTimeout(closeGroupTimeout);
@@ -378,25 +379,30 @@ const createEventGroupLogger = () => {
378
379
  console.group(label);
379
380
  currentInitiator = initiator;
380
381
  }
381
- const line = formatSideEffectLine(e, sideEffect);
382
- console.debug(line, ...args);
382
+ const line = formatSideEffectLine(e, category);
383
+ console.debug(`%c${line}`, `color:${color};font-weight:bold`, ...args);
383
384
  scheduleGroupEnd();
384
385
  };
386
+
387
+ return {
388
+ createCategory: (name, color = "inherit") => {
389
+ return (e, ...args) => {
390
+ log(name, color, e, ...args);
391
+ };
392
+ },
393
+ };
385
394
  };
386
395
 
387
- const formatSideEffectLine = (e, sideEffect) => {
388
- const parts = [];
396
+ const formatSideEffectLine = (e, prefix) => {
397
+ const parts = [prefix];
389
398
  const chain = e.detail?.eventChain;
390
399
  if (chain) {
391
- // chain[0] is the root event, already shown as the group label — skip it
400
+ // chain[0] is the root event, already shown as the group label — skip it.
401
+ // chain includes the direct parent (e.detail.event) as its last element.
392
402
  for (const chainedEvent of chain.slice(1)) {
393
403
  parts.push(chainedEvent.type);
394
404
  }
395
- if (e.detail?.event) {
396
- parts.push(e.detail.event.type);
397
- }
398
405
  }
399
- parts.push(sideEffect);
400
406
  return parts.join(" -> ");
401
407
  };
402
408
 
@@ -4197,6 +4203,19 @@ const getFocusVisibilityInfo = (node, { excludeAriaHidden } = {}) => {
4197
4203
  ) {
4198
4204
  return { visible: false, reason: "inside closed popover element" };
4199
4205
  }
4206
+ // Open popovers and open dialogs render in the top layer: they escape
4207
+ // the normal layout/stacking context of their DOM ancestors.
4208
+ // No need to check further up the tree.
4209
+ if (elementIsDialog(nodeOrAncestor) && nodeOrAncestor.open) {
4210
+ break;
4211
+ }
4212
+ if (
4213
+ nodeOrAncestor.popover !== null &&
4214
+ nodeOrAncestor.popover !== undefined &&
4215
+ nodeOrAncestor.matches(":popover-open")
4216
+ ) {
4217
+ break;
4218
+ }
4200
4219
  nodeOrAncestor = nodeOrAncestor.parentNode;
4201
4220
  }
4202
4221
  return { visible: true, reason: "no reason to be hidden" };
@@ -4364,9 +4383,16 @@ const canInteract = (element) => {
4364
4383
  * @returns {Element|null}
4365
4384
  */
4366
4385
  const findFocusDelegateTarget = (el) => {
4367
- if (!el.hasAttribute("navi-focus-delegate")) {
4386
+ const naviFocusDelegate = el.getAttribute("navi-focus-delegate");
4387
+ if (naviFocusDelegate === null || naviFocusDelegate === undefined) {
4368
4388
  return null;
4369
4389
  }
4390
+ if (naviFocusDelegate) {
4391
+ const delegateTarget = document.getElementById(naviFocusDelegate);
4392
+ if (delegateTarget && elementIsFocusable(delegateTarget)) {
4393
+ return delegateTarget;
4394
+ }
4395
+ }
4370
4396
  let ancestor = el.parentElement;
4371
4397
  while (ancestor) {
4372
4398
  if (elementIsFocusable(ancestor)) {
@@ -15081,4 +15107,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
15081
15107
  };
15082
15108
  };
15083
15109
 
15084
- export { EASING, activeElementSignal, addActiveElementEffect, addAttributeEffect, allowWheelThrough, appendStyles, captureScrollState, 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 };
15110
+ 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.14.2",
3
+ "version": "0.14.3",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {