@jsenv/dom 0.14.1 → 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 +73 -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,9 +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,
230
+ cancelable: true,
229
231
  });
232
+ chainEvent(customEvent, customEventDetail?.event);
230
233
  return el.dispatchEvent(customEvent);
231
234
  };
232
235
 
@@ -238,26 +241,28 @@ const dispatchPublicCustomEvent = (
238
241
  */
239
242
  const dispatchCustomEvent = (el, customEventName, customEventDetail) => {
240
243
  const customEvent = new CustomEvent(customEventName, {
241
- detail: resolveEventDetail(customEventDetail),
244
+ detail: customEventDetail,
242
245
  cancelable: true,
243
246
  });
247
+ chainEvent(customEvent, customEventDetail?.event);
244
248
  const result = el.dispatchEvent(customEvent);
245
249
  return result;
246
250
  };
247
251
 
248
- const resolveEventDetail = (customEventDetail) => {
249
- const { event, ...rest } = customEventDetail ?? {};
250
- const isWrappedCustomEvent = event?.detail?.event !== undefined;
251
- if (!isWrappedCustomEvent) {
252
- return { ...rest, event };
252
+ const chainEvent = (customEvent, parentEvent) => {
253
+ if (!parentEvent) {
254
+ return customEvent;
253
255
  }
254
- // Keep `event` as the direct parent so callers see the immediate facade.
255
- // Build eventChain as [root, ...grandparents] — oldest first, excluding `event`.
256
- 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;
257
260
  const eventChain = previousChain
258
- ? [...previousChain, event.detail.event]
259
- : [event.detail.event];
260
- return { ...rest, event, eventChain };
261
+ ? [...previousChain, parentEvent]
262
+ : [parentEvent];
263
+ customEvent.detail.event = parentEvent;
264
+ customEvent.detail.eventChain = eventChain;
265
+ return customEvent;
261
266
  };
262
267
 
263
268
  /**
@@ -287,12 +292,6 @@ const findEvent = (event, predicate) => {
287
292
  }
288
293
  }
289
294
  }
290
- const initiator = event.detail?.event;
291
- if (initiator) {
292
- if (match(initiator)) {
293
- return initiator;
294
- }
295
- }
296
295
  return undefined;
297
296
  };
298
297
 
@@ -313,17 +312,15 @@ const resolveEventPredicate = (predicate) => {
313
312
  */
314
313
  const formatEventSideEffect = (e, sideEffect) => {
315
314
  const parts = [];
316
- if (e.detail?.event !== undefined) {
315
+ if (e.detail?.eventChain) {
317
316
  const chain = e.detail.eventChain;
318
- const initiator = chain ? chain[0] : e.detail.event;
317
+ const initiator = chain[0];
319
318
  parts.push(
320
319
  `"${initiator.type}" on ${getElementSignature(initiator.target)}`,
321
320
  );
322
- if (chain) {
323
- for (const chainedEvent of chain.slice(1)) {
324
- parts.push(chainedEvent.type);
325
- }
326
- 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);
327
324
  }
328
325
  parts.push(e.type);
329
326
  } else {
@@ -334,11 +331,12 @@ const formatEventSideEffect = (e, sideEffect) => {
334
331
 
335
332
  /**
336
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.
337
335
  *
338
336
  * Usage:
339
- * const log = createEventGroupLogger();
340
- * log(e, "navi_action_requested"); // opens/reuses a group for the initiator event
341
- * 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
342
340
  *
343
341
  * The group closes automatically after the current JS task completes (setTimeout 0).
344
342
  */
@@ -357,14 +355,18 @@ const createEventGroupLogger = () => {
357
355
  }, 0);
358
356
  };
359
357
 
360
- return (eOrMessage, sideEffect) => {
361
- if (!(eOrMessage instanceof Event)) {
362
- 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
+ );
363
366
  return;
364
367
  }
365
- const e = eOrMessage;
366
368
  const chain = e.detail?.eventChain;
367
- const initiator = chain ? chain[0] : (e.detail?.event ?? e);
369
+ const initiator = chain ? chain[0] : e;
368
370
  if (initiator !== currentInitiator) {
369
371
  if (currentInitiator !== null) {
370
372
  clearTimeout(closeGroupTimeout);
@@ -377,25 +379,30 @@ const createEventGroupLogger = () => {
377
379
  console.group(label);
378
380
  currentInitiator = initiator;
379
381
  }
380
- const line = formatSideEffectLine(e, sideEffect);
381
- console.debug(line);
382
+ const line = formatSideEffectLine(e, category);
383
+ console.debug(`%c${line}`, `color:${color};font-weight:bold`, ...args);
382
384
  scheduleGroupEnd();
383
385
  };
386
+
387
+ return {
388
+ createCategory: (name, color = "inherit") => {
389
+ return (e, ...args) => {
390
+ log(name, color, e, ...args);
391
+ };
392
+ },
393
+ };
384
394
  };
385
395
 
386
- const formatSideEffectLine = (e, sideEffect) => {
387
- const parts = [];
396
+ const formatSideEffectLine = (e, prefix) => {
397
+ const parts = [prefix];
388
398
  const chain = e.detail?.eventChain;
389
399
  if (chain) {
390
- // 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.
391
402
  for (const chainedEvent of chain.slice(1)) {
392
403
  parts.push(chainedEvent.type);
393
404
  }
394
- if (e.detail?.event) {
395
- parts.push(e.detail.event.type);
396
- }
397
405
  }
398
- parts.push(sideEffect);
399
406
  return parts.join(" -> ");
400
407
  };
401
408
 
@@ -4196,6 +4203,19 @@ const getFocusVisibilityInfo = (node, { excludeAriaHidden } = {}) => {
4196
4203
  ) {
4197
4204
  return { visible: false, reason: "inside closed popover element" };
4198
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
+ }
4199
4219
  nodeOrAncestor = nodeOrAncestor.parentNode;
4200
4220
  }
4201
4221
  return { visible: true, reason: "no reason to be hidden" };
@@ -4363,9 +4383,16 @@ const canInteract = (element) => {
4363
4383
  * @returns {Element|null}
4364
4384
  */
4365
4385
  const findFocusDelegateTarget = (el) => {
4366
- if (!el.hasAttribute("navi-focus-delegate")) {
4386
+ const naviFocusDelegate = el.getAttribute("navi-focus-delegate");
4387
+ if (naviFocusDelegate === null || naviFocusDelegate === undefined) {
4367
4388
  return null;
4368
4389
  }
4390
+ if (naviFocusDelegate) {
4391
+ const delegateTarget = document.getElementById(naviFocusDelegate);
4392
+ if (delegateTarget && elementIsFocusable(delegateTarget)) {
4393
+ return delegateTarget;
4394
+ }
4395
+ }
4369
4396
  let ancestor = el.parentElement;
4370
4397
  while (ancestor) {
4371
4398
  if (elementIsFocusable(ancestor)) {
@@ -15080,4 +15107,4 @@ const useResizeStatus = (elementRef, { as = "number" } = {}) => {
15080
15107
  };
15081
15108
  };
15082
15109
 
15083
- 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.1",
3
+ "version": "0.14.3",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {