@jsenv/dom 0.6.0 → 0.7.0

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 (109) hide show
  1. package/dist/jsenv_dom.js +262 -330
  2. package/package.json +2 -4
  3. package/index.js +0 -124
  4. package/src/attr/add_attribute_effect.js +0 -93
  5. package/src/attr/attributes.js +0 -32
  6. package/src/color/color_constrast.js +0 -69
  7. package/src/color/color_parsing.js +0 -319
  8. package/src/color/color_scheme.js +0 -28
  9. package/src/color/pick_light_or_dark.js +0 -34
  10. package/src/color/resolve_css_color.js +0 -60
  11. package/src/demos/3_columns_resize_demo.html +0 -84
  12. package/src/demos/3_rows_resize_demo.html +0 -89
  13. package/src/demos/aside_and_main_demo.html +0 -93
  14. package/src/demos/coordinates_demo.html +0 -450
  15. package/src/demos/document_autoscroll_demo.html +0 -517
  16. package/src/demos/drag_gesture_constraints_demo.html +0 -701
  17. package/src/demos/drag_gesture_demo.html +0 -1047
  18. package/src/demos/drag_gesture_element_to_impact_demo.html +0 -445
  19. package/src/demos/drag_reference_element_demo.html +0 -480
  20. package/src/demos/flex_details_set_demo.html +0 -302
  21. package/src/demos/flex_details_set_demo_2.html +0 -315
  22. package/src/demos/visible_rect_demo.html +0 -525
  23. package/src/element_signature.js +0 -100
  24. package/src/interaction/drag/constraint_feedback_line.js +0 -92
  25. package/src/interaction/drag/drag_constraint.js +0 -659
  26. package/src/interaction/drag/drag_debug_markers.js +0 -635
  27. package/src/interaction/drag/drag_element_positioner.js +0 -382
  28. package/src/interaction/drag/drag_gesture.js +0 -566
  29. package/src/interaction/drag/drag_resize_demo.html +0 -571
  30. package/src/interaction/drag/drag_to_move.js +0 -301
  31. package/src/interaction/drag/drag_to_resize_gesture.js +0 -68
  32. package/src/interaction/drag/drop_target_detection.js +0 -148
  33. package/src/interaction/drag/sticky_frontiers.js +0 -160
  34. package/src/interaction/event_marker.js +0 -14
  35. package/src/interaction/focus/active_element.js +0 -33
  36. package/src/interaction/focus/arrow_navigation.js +0 -599
  37. package/src/interaction/focus/element_is_focusable.js +0 -57
  38. package/src/interaction/focus/element_visibility.js +0 -111
  39. package/src/interaction/focus/find_focusable.js +0 -21
  40. package/src/interaction/focus/focus_group.js +0 -91
  41. package/src/interaction/focus/focus_group_registry.js +0 -12
  42. package/src/interaction/focus/focus_nav.js +0 -12
  43. package/src/interaction/focus/focus_nav_event_marker.js +0 -14
  44. package/src/interaction/focus/focus_trap.js +0 -105
  45. package/src/interaction/focus/tab_navigation.js +0 -128
  46. package/src/interaction/focus/tests/focus_group_skip_tab_test.html +0 -206
  47. package/src/interaction/focus/tests/tree_focus_test.html +0 -304
  48. package/src/interaction/focus/tests/tree_focus_test.jsx +0 -261
  49. package/src/interaction/focus/tests/tree_focus_test_preact.html +0 -13
  50. package/src/interaction/isolate_interactions.js +0 -161
  51. package/src/interaction/keyboard.js +0 -26
  52. package/src/interaction/scroll/capture_scroll.js +0 -47
  53. package/src/interaction/scroll/is_scrollable.js +0 -159
  54. package/src/interaction/scroll/scroll_container.js +0 -110
  55. package/src/interaction/scroll/scroll_trap.js +0 -44
  56. package/src/interaction/scroll/scrollbar_size.js +0 -20
  57. package/src/interaction/scroll/wheel_through.js +0 -138
  58. package/src/iterable_weak_set.js +0 -66
  59. package/src/position/dom_coords.js +0 -340
  60. package/src/position/offset_parent.js +0 -15
  61. package/src/position/position_fixed.js +0 -15
  62. package/src/position/position_sticky.js +0 -213
  63. package/src/position/sticky_rect.js +0 -79
  64. package/src/position/visible_rect.js +0 -486
  65. package/src/pub_sub.js +0 -31
  66. package/src/size/can_take_size.js +0 -11
  67. package/src/size/details_content_full_height.js +0 -63
  68. package/src/size/flex_details_set.js +0 -974
  69. package/src/size/get_available_height.js +0 -22
  70. package/src/size/get_available_width.js +0 -22
  71. package/src/size/get_border_sizes.js +0 -14
  72. package/src/size/get_height.js +0 -4
  73. package/src/size/get_inner_height.js +0 -15
  74. package/src/size/get_inner_width.js +0 -15
  75. package/src/size/get_margin_sizes.js +0 -10
  76. package/src/size/get_max_height.js +0 -57
  77. package/src/size/get_max_width.js +0 -47
  78. package/src/size/get_min_height.js +0 -14
  79. package/src/size/get_min_width.js +0 -14
  80. package/src/size/get_padding_sizes.js +0 -10
  81. package/src/size/get_width.js +0 -4
  82. package/src/size/hooks/use_available_height.js +0 -27
  83. package/src/size/hooks/use_available_width.js +0 -27
  84. package/src/size/hooks/use_max_height.js +0 -10
  85. package/src/size/hooks/use_max_width.js +0 -10
  86. package/src/size/hooks/use_resize_status.js +0 -62
  87. package/src/size/resize.js +0 -695
  88. package/src/size/resolve_css_size.js +0 -32
  89. package/src/style/dom_styles.js +0 -97
  90. package/src/style/style_composition.js +0 -121
  91. package/src/style/style_controller.js +0 -345
  92. package/src/style/style_default.js +0 -153
  93. package/src/style/style_default_demo.html +0 -128
  94. package/src/style/style_parsing.js +0 -375
  95. package/src/transition/demos/animation_resumption_test.xhtml +0 -500
  96. package/src/transition/demos/height_toggle_test.xhtml +0 -515
  97. package/src/transition/dom_transition.js +0 -254
  98. package/src/transition/easing.js +0 -48
  99. package/src/transition/group_transition.js +0 -261
  100. package/src/transition/transform_style_parser.js +0 -32
  101. package/src/transition/transition_playback.js +0 -366
  102. package/src/transition/transition_timeline.js +0 -79
  103. package/src/traversal.js +0 -247
  104. package/src/ui_transition/demos/content_states_transition_demo.html +0 -628
  105. package/src/ui_transition/demos/smooth_height_transition_demo.html +0 -149
  106. package/src/ui_transition/demos/transition_testing.html +0 -354
  107. package/src/ui_transition/ui_transition.js +0 -1491
  108. package/src/utils.js +0 -69
  109. package/src/value_effect.js +0 -35
@@ -1,111 +0,0 @@
1
- import { getStyle } from "../../style/dom_styles.js";
2
- import {
3
- elementIsDetails,
4
- elementIsSummary,
5
- isDocumentElement,
6
- } from "../../utils.js";
7
-
8
- export const elementIsVisibleForFocus = (node) => {
9
- return getFocusVisibilityInfo(node).visible;
10
- };
11
- export const getFocusVisibilityInfo = (node) => {
12
- if (isDocumentElement(node)) {
13
- return { visible: true, reason: "is document" };
14
- }
15
- if (node.hasAttribute("hidden")) {
16
- return { visible: false, reason: "has hidden attribute" };
17
- }
18
- if (getStyle(node, "visibility") === "hidden") {
19
- return { visible: false, reason: "uses visiblity: hidden" };
20
- }
21
- if (node.tagName === "INPUT" && node.type === "hidden") {
22
- return { visible: false, reason: "input type hidden" };
23
- }
24
- let nodeOrAncestor = node;
25
- while (nodeOrAncestor) {
26
- if (isDocumentElement(nodeOrAncestor)) {
27
- break;
28
- }
29
- if (getStyle(nodeOrAncestor, "display") === "none") {
30
- return { visible: false, reason: "ancestor uses display: none" };
31
- }
32
- // Check if element is inside a closed details element
33
- if (elementIsDetails(nodeOrAncestor) && !nodeOrAncestor.open) {
34
- // Special case: summary elements are visible even when their parent details is closed
35
- // But only if this details element is the direct parent of the summary
36
- if (!elementIsSummary(node) || node.parentElement !== nodeOrAncestor) {
37
- return { visible: false, reason: "inside closed details element" };
38
- }
39
- // Continue checking ancestors
40
- }
41
- nodeOrAncestor = nodeOrAncestor.parentNode;
42
- }
43
- return { visible: true, reason: "no reason to be hidden" };
44
- };
45
-
46
- export const elementIsVisuallyVisible = (node, options = {}) => {
47
- return getVisuallyVisibleInfo(node, options).visible;
48
- };
49
- export const getVisuallyVisibleInfo = (
50
- node,
51
- { countOffscreenAsVisible = false } = {},
52
- ) => {
53
- // First check all the focusable visibility conditions
54
- const focusVisibilityInfo = getFocusVisibilityInfo(node);
55
- if (!focusVisibilityInfo.visible) {
56
- return focusVisibilityInfo;
57
- }
58
-
59
- // Additional visual visibility checks
60
- if (getStyle(node, "opacity") === "0") {
61
- return { visible: false, reason: "uses opacity: 0" };
62
- }
63
-
64
- const rect = node.getBoundingClientRect();
65
- if (rect.width === 0 && rect.height === 0) {
66
- return { visible: false, reason: "has zero dimensions" };
67
- }
68
-
69
- // Check for clipping
70
- const clipStyle = getStyle(node, "clip");
71
- if (clipStyle && clipStyle !== "auto" && clipStyle.includes("rect(0")) {
72
- return { visible: false, reason: "clipped with clip property" };
73
- }
74
-
75
- const clipPathStyle = getStyle(node, "clip-path");
76
- if (clipPathStyle && clipPathStyle.includes("inset(100%")) {
77
- return { visible: false, reason: "clipped with clip-path" };
78
- }
79
-
80
- // Check if positioned off-screen (unless option says to count as visible)
81
- if (!countOffscreenAsVisible) {
82
- if (
83
- rect.right < 0 ||
84
- rect.bottom < 0 ||
85
- rect.left > window.innerWidth ||
86
- rect.top > window.innerHeight
87
- ) {
88
- return { visible: false, reason: "positioned off-screen" };
89
- }
90
- }
91
-
92
- // Check for transform scale(0)
93
- const transformStyle = getStyle(node, "transform");
94
- if (transformStyle && transformStyle.includes("scale(0")) {
95
- return { visible: false, reason: "scaled to zero with transform" };
96
- }
97
-
98
- return { visible: true, reason: "visually visible" };
99
- };
100
- export const getFirstVisuallyVisibleAncestor = (node, options = {}) => {
101
- let ancestorCandidate = node.parentNode;
102
- while (ancestorCandidate) {
103
- const visibilityInfo = getVisuallyVisibleInfo(ancestorCandidate, options);
104
- if (visibilityInfo.visible) {
105
- return ancestorCandidate;
106
- }
107
- ancestorCandidate = ancestorCandidate.parentElement;
108
- }
109
- // This shouldn't happen in normal cases since document element is always visible
110
- return null;
111
- };
@@ -1,21 +0,0 @@
1
- import { findDescendant } from "../../traversal.js";
2
- import { getAssociatedElements } from "../../utils.js";
3
- import { elementIsFocusable } from "./element_is_focusable.js";
4
-
5
- export const findFocusable = (element) => {
6
- const associatedElements = getAssociatedElements(element);
7
- if (associatedElements) {
8
- for (const associatedElement of associatedElements) {
9
- const focusable = findFocusable(associatedElement);
10
- if (focusable) {
11
- return focusable;
12
- }
13
- }
14
- return null;
15
- }
16
- if (elementIsFocusable(element)) {
17
- return element;
18
- }
19
- const focusableDescendant = findDescendant(element, elementIsFocusable);
20
- return focusableDescendant;
21
- };
@@ -1,91 +0,0 @@
1
- /**
2
- *
3
- - https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md
4
- - https://open-ui.org/components/focusgroup.explainer/
5
- - https://github.com/openui/open-ui/issues/990
6
-
7
- - https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Focusgroup/explainer.md#69-grid-focusgroups
8
- */
9
-
10
- import { performArrowNavigation } from "./arrow_navigation.js";
11
- import { setFocusGroup } from "./focus_group_registry.js";
12
- import { isFocusNavMarked } from "./focus_nav_event_marker.js";
13
- import { performTabNavigation } from "./tab_navigation.js";
14
-
15
- export const initFocusGroup = (
16
- element,
17
- {
18
- direction = "both",
19
- // extend = true,
20
- skipTab = true,
21
- loop = false,
22
- name, // Can be undefined for implicit ancestor-descendant grouping
23
- } = {},
24
- ) => {
25
- const cleanupCallbackSet = new Set();
26
- const cleanup = () => {
27
- for (const callback of cleanupCallbackSet) {
28
- callback();
29
- }
30
- cleanupCallbackSet.clear();
31
- };
32
-
33
- // Store focus group data in registry
34
- const removeFocusGroup = setFocusGroup(element, {
35
- direction,
36
- loop,
37
- name, // Store undefined as-is for implicit grouping
38
- });
39
- cleanupCallbackSet.add(removeFocusGroup);
40
-
41
- tab: {
42
- if (!skipTab) {
43
- break tab;
44
- }
45
- const handleTabKeyDown = (event) => {
46
- if (isFocusNavMarked(event)) {
47
- // Prevent double handling of the same event + allow preventing focus nav from outside
48
- return;
49
- }
50
- performTabNavigation(event, { outsideOfElement: element });
51
- };
52
- // Handle Tab navigation (exit group)
53
- element.addEventListener("keydown", handleTabKeyDown, {
54
- // we must use capture: false to let chance for other part of the code
55
- // to call preventFocusNav
56
- capture: false,
57
- passive: false,
58
- });
59
- cleanupCallbackSet.add(() => {
60
- element.removeEventListener("keydown", handleTabKeyDown, {
61
- capture: false,
62
- passive: false,
63
- });
64
- });
65
- }
66
-
67
- // Handle Arrow key navigation (within group)
68
- arrow_keys: {
69
- const handleArrowKeyDown = (event) => {
70
- if (isFocusNavMarked(event)) {
71
- // Prevent double handling of the same event + allow preventing focus nav from outside
72
- return;
73
- }
74
- performArrowNavigation(event, element, { direction, loop, name });
75
- };
76
- element.addEventListener("keydown", handleArrowKeyDown, {
77
- // we must use capture: false to let chance for other part of the code
78
- // to call preventFocusNav
79
- capture: false,
80
- passive: false,
81
- });
82
- cleanupCallbackSet.add(() => {
83
- element.removeEventListener("keydown", handleArrowKeyDown, {
84
- capture: false,
85
- passive: false,
86
- });
87
- });
88
- }
89
-
90
- return { cleanup };
91
- };
@@ -1,12 +0,0 @@
1
- // WeakMap to store focus group metadata
2
- export const focusGroupRegistry = new WeakMap();
3
-
4
- export const setFocusGroup = (element, options) => {
5
- focusGroupRegistry.set(element, options);
6
- return () => {
7
- focusGroupRegistry.delete(element);
8
- };
9
- };
10
- export const getFocusGroup = (element) => {
11
- return focusGroupRegistry.get(element);
12
- };
@@ -1,12 +0,0 @@
1
- import { preventFocusNav } from "./focus_nav_event_marker.js";
2
-
3
- export const preventFocusNavViaKeyboard = (keyboardEvent) => {
4
- if (keyboardEvent.key === "Tab") {
5
- // prevent tab to move focus
6
- keyboardEvent.preventDefault();
7
- return true;
8
- }
9
- // ensure we won't perform our internal focus nav in focus groups
10
- preventFocusNav(keyboardEvent);
11
- return false;
12
- };
@@ -1,14 +0,0 @@
1
- import { createEventMarker } from "../event_marker.js";
2
-
3
- export const focusNavEventMarker = createEventMarker("focus_nav");
4
-
5
- export const preventFocusNav = (event) => {
6
- focusNavEventMarker.mark(event);
7
- };
8
-
9
- export const isFocusNavMarked = (event) => {
10
- return focusNavEventMarker.isMarked(event);
11
- };
12
- export const markFocusNav = (event) => {
13
- focusNavEventMarker.mark(event);
14
- };
@@ -1,105 +0,0 @@
1
- import { performTabNavigation } from "./tab_navigation.js";
2
-
3
- export const trapFocusInside = (element) => {
4
- if (element.nodeType === 3) {
5
- console.warn("cannot trap focus inside a text node");
6
- return () => {};
7
- }
8
-
9
- const trappedElement = activeTraps.find(
10
- (activeTrap) => activeTrap.element === element,
11
- );
12
- if (trappedElement) {
13
- console.warn("focus already trapped inside this element");
14
- return () => {};
15
- }
16
-
17
- const isEventOutside = (event) => {
18
- if (event.target === element) return false;
19
- if (element.contains(event.target)) return false;
20
- return true;
21
- };
22
-
23
- const lock = () => {
24
- const onmousedown = (event) => {
25
- if (isEventOutside(event)) {
26
- event.preventDefault();
27
- event.stopImmediatePropagation();
28
- }
29
- };
30
-
31
- const onkeydown = (event) => {
32
- if (isTabEvent(event)) {
33
- performTabNavigation(event, { rootElement: element });
34
- }
35
- };
36
-
37
- document.addEventListener("mousedown", onmousedown, {
38
- capture: true,
39
- passive: false,
40
- });
41
- document.addEventListener("keydown", onkeydown, {
42
- capture: true,
43
- passive: false,
44
- });
45
-
46
- return () => {
47
- document.removeEventListener("mousedown", onmousedown, {
48
- capture: true,
49
- passive: false,
50
- });
51
- document.removeEventListener("keydown", onkeydown, {
52
- capture: true,
53
- passive: false,
54
- });
55
- };
56
- };
57
-
58
- const deactivate = activate({
59
- // element
60
- lock,
61
- });
62
-
63
- const untrap = () => {
64
- deactivate();
65
- };
66
-
67
- return untrap;
68
- };
69
-
70
- const isTabEvent = (event) => event.key === "Tab" || event.keyCode === 9;
71
-
72
- const activeTraps = [];
73
- const activate = ({ lock }) => {
74
- // unlock any trap currently activated
75
- let previousTrap;
76
- if (activeTraps.length > 0) {
77
- previousTrap = activeTraps[activeTraps.length - 1];
78
- previousTrap.unlock();
79
- }
80
-
81
- // store trap methods to lock/unlock as traps are acivated/deactivated
82
- const trap = { lock, unlock: lock() };
83
- activeTraps.push(trap);
84
-
85
- return () => {
86
- if (activeTraps.length === 0) {
87
- console.warn("cannot deactivate an already deactivated trap");
88
- return;
89
- }
90
- const lastTrap = activeTraps[activeTraps.length - 1];
91
- if (trap !== lastTrap) {
92
- // TODO: investigate this and maybe remove this requirment
93
- console.warn(
94
- "you must deactivate trap in the same order they were activated",
95
- );
96
- return;
97
- }
98
- activeTraps.pop();
99
- trap.unlock();
100
- // if any,reactivate the previous trap
101
- if (previousTrap) {
102
- previousTrap.unlock = previousTrap.lock();
103
- }
104
- };
105
- };
@@ -1,128 +0,0 @@
1
- import {
2
- findAfter,
3
- findBefore,
4
- findDescendant,
5
- findLastDescendant,
6
- } from "../../traversal.js";
7
- import { elementIsFocusable } from "./element_is_focusable.js";
8
- import { markFocusNav } from "./focus_nav_event_marker.js";
9
-
10
- const DEBUG = true;
11
-
12
- export const performTabNavigation = (
13
- event,
14
- { rootElement = document.body, outsideOfElement = null } = {},
15
- ) => {
16
- if (!isTabEvent(event)) {
17
- return false;
18
- }
19
- const activeElement = document.activeElement;
20
- if (activeElement.getAttribute("data-focusnav") === "none") {
21
- event.preventDefault(); // ensure tab cannot move focus
22
- return true;
23
- }
24
- const isForward = !event.shiftKey;
25
- const onTargetToFocus = (targetToFocus) => {
26
- console.debug(
27
- `Tab navigation: ${isForward ? "forward" : "backward"} from`,
28
- activeElement,
29
- "to",
30
- targetToFocus,
31
- );
32
- event.preventDefault();
33
- markFocusNav(event);
34
- targetToFocus.focus();
35
- };
36
-
37
- if (DEBUG) {
38
- console.debug(
39
- `Tab navigation: ${isForward ? "forward" : "backward"} from,`,
40
- activeElement,
41
- );
42
- }
43
-
44
- const predicate = (candidate) => {
45
- const canBeFocusedByTab = isFocusableByTab(candidate);
46
- if (DEBUG) {
47
- console.debug(`Testing`, candidate, `${canBeFocusedByTab ? "✓" : "✗"}`);
48
- }
49
- return canBeFocusedByTab;
50
- };
51
-
52
- const activeElementIsRoot = activeElement === rootElement;
53
- forward: {
54
- if (!isForward) {
55
- break forward;
56
- }
57
- if (activeElementIsRoot) {
58
- const firstFocusableElement = findDescendant(activeElement, predicate, {
59
- skipRoot: outsideOfElement,
60
- });
61
- if (firstFocusableElement) {
62
- return onTargetToFocus(firstFocusableElement);
63
- }
64
- return false;
65
- }
66
- const nextFocusableElement = findAfter(activeElement, predicate, {
67
- root: rootElement,
68
- skipRoot: outsideOfElement,
69
- });
70
- if (nextFocusableElement) {
71
- return onTargetToFocus(nextFocusableElement);
72
- }
73
- const firstFocusableElement = findDescendant(activeElement, predicate, {
74
- skipRoot: outsideOfElement,
75
- });
76
- if (firstFocusableElement) {
77
- return onTargetToFocus(firstFocusableElement);
78
- }
79
- return false;
80
- }
81
-
82
- backward: {
83
- if (activeElementIsRoot) {
84
- const lastFocusableElement = findLastDescendant(
85
- activeElement,
86
- predicate,
87
- {
88
- skipRoot: outsideOfElement,
89
- },
90
- );
91
- if (lastFocusableElement) {
92
- return onTargetToFocus(lastFocusableElement);
93
- }
94
- return false;
95
- }
96
-
97
- const previousFocusableElement = findBefore(activeElement, predicate, {
98
- root: rootElement,
99
- skipRoot: outsideOfElement,
100
- });
101
- if (previousFocusableElement) {
102
- return onTargetToFocus(previousFocusableElement);
103
- }
104
- const lastFocusableElement = findLastDescendant(activeElement, predicate, {
105
- skipRoot: outsideOfElement,
106
- });
107
- if (lastFocusableElement) {
108
- return onTargetToFocus(lastFocusableElement);
109
- }
110
- return false;
111
- }
112
- };
113
-
114
- export const isTabEvent = (event) => event.key === "Tab" || event.keyCode === 9;
115
-
116
- const isFocusableByTab = (element) => {
117
- if (hasNegativeTabIndex(element)) {
118
- return false;
119
- }
120
- return elementIsFocusable(element);
121
- };
122
- const hasNegativeTabIndex = (element) => {
123
- return (
124
- element.hasAttribute &&
125
- element.hasAttribute("tabIndex") &&
126
- Number(element.getAttribute("tabindex")) < 0
127
- );
128
- };
@@ -1,206 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Focus Group Test - Skip Tab</title>
7
- <style>
8
- body {
9
- font-family:
10
- system-ui,
11
- -apple-system,
12
- sans-serif;
13
- margin: 40px;
14
- line-height: 1.6;
15
- }
16
-
17
- .test-section {
18
- margin: 30px 0;
19
- padding: 20px;
20
- border: 2px solid #e0e0e0;
21
- border-radius: 8px;
22
- }
23
-
24
- .focus-group {
25
- background: #f5f5f5;
26
- border: 2px dashed #999;
27
- padding: 15px;
28
- margin: 10px 0;
29
- border-radius: 4px;
30
- }
31
-
32
- .focus-group h3 {
33
- margin-top: 0;
34
- color: #666;
35
- }
36
-
37
- button,
38
- input,
39
- select,
40
- textarea {
41
- margin: 5px;
42
- padding: 8px 12px;
43
- border: 1px solid #ccc;
44
- border-radius: 4px;
45
- font-size: 14px;
46
- }
47
-
48
- button:focus,
49
- input:focus,
50
- select:focus,
51
- textarea:focus {
52
- outline: 2px solid #007acc;
53
- outline-offset: 2px;
54
- }
55
-
56
- .instructions {
57
- background: #e8f4fd;
58
- padding: 15px;
59
- border-radius: 4px;
60
- margin-bottom: 20px;
61
- }
62
-
63
- .status {
64
- position: fixed;
65
- top: 10px;
66
- right: 10px;
67
- background: #333;
68
- color: white;
69
- padding: 10px;
70
- border-radius: 4px;
71
- font-family: monospace;
72
- font-size: 12px;
73
- }
74
- </style>
75
- </head>
76
- <body>
77
- <div class="status" id="status">Focus: none</div>
78
-
79
- <h1>Focus Group Test - Skip Tab</h1>
80
-
81
- <div class="instructions">
82
- <strong>Test Instructions:</strong>
83
- <ol>
84
- <li>Use <kbd>Tab</kbd> to navigate through focusable elements</li>
85
- <li>
86
- Elements in the focus group are still accessible (click them or
87
- navigate normally)
88
- </li>
89
- <li>
90
- When you're focused on an element INSIDE the focus group and press
91
- <kbd>Tab</kbd>, it should jump to the next focusable element OUTSIDE
92
- the group
93
- </li>
94
- <li>
95
- Elements before and after the focus group should have normal tab
96
- behavior
97
- </li>
98
- <li>
99
- Try clicking on elements inside the focus group, then pressing
100
- <kbd>Tab</kbd>
101
- </li>
102
- </ol>
103
- </div>
104
-
105
- <div class="test-section">
106
- <h2>Before Focus Group</h2>
107
- <button>Button Before 1</button>
108
- <input type="text" placeholder="Input Before 1" />
109
- <button>Button Before 2</button>
110
- </div>
111
-
112
- <div class="test-section">
113
- <h2>Focus Group (skipTab = true)</h2>
114
- <div class="focus-group" id="focus-group-1">
115
- <h3>� Tab from inside this group jumps outside</h3>
116
- <button>Button in Group 1</button>
117
- <input type="text" placeholder="Input in Group 1" />
118
- <select>
119
- <option>Option 1</option>
120
- <option>Option 2</option>
121
- </select>
122
- <button>Button in Group 2</button>
123
- <textarea placeholder="Textarea in Group"></textarea>
124
- <button>Button in Group 3</button>
125
- </div>
126
- </div>
127
-
128
- <div class="test-section">
129
- <h2>After Focus Group</h2>
130
- <button>Button After 1</button>
131
- <input type="text" placeholder="Input After 1" />
132
- <button>Button After 2</button>
133
- </div>
134
-
135
- <div class="test-section">
136
- <h2>Control Group (no focus group)</h2>
137
- <div class="focus-group" style="border-color: #4caf50">
138
- <h3>✅ This group has normal tab behavior</h3>
139
- <button>Normal Button 1</button>
140
- <input type="text" placeholder="Normal Input 1" />
141
- <button>Normal Button 2</button>
142
- </div>
143
- </div>
144
-
145
- <script type="module">
146
- import { initFocusGroup } from "../focus_group.js";
147
-
148
- // Initialize the focus group with skipTab enabled
149
- const focusGroup = document.getElementById("focus-group-1");
150
- initFocusGroup(focusGroup, {
151
- skipTab: true,
152
- });
153
-
154
- // Status tracking for debugging
155
- const statusEl = document.getElementById("status");
156
-
157
- function updateStatus() {
158
- const activeElement = document.activeElement;
159
- const tagName = activeElement.tagName.toLowerCase();
160
- const text =
161
- activeElement.textContent ||
162
- activeElement.placeholder ||
163
- activeElement.value ||
164
- "";
165
- const truncatedText =
166
- text.length > 20 ? `${text.substring(0, 20)}...` : text;
167
-
168
- statusEl.textContent = `Focus: ${tagName}${truncatedText ? ` (${truncatedText})` : ""}`;
169
- }
170
-
171
- // Track focus changes
172
- document.addEventListener("focusin", updateStatus);
173
- document.addEventListener("focusout", updateStatus);
174
-
175
- // Initial status
176
- updateStatus();
177
-
178
- // Add some debugging
179
- focusGroup.addEventListener(
180
- "keydown",
181
- (e) => {
182
- if (e.key === "Tab") {
183
- console.log("Tab pressed in focus group", {
184
- target: e.target,
185
- shiftKey: e.shiftKey,
186
- defaultPrevented: e.defaultPrevented,
187
- });
188
- }
189
- },
190
- { capture: true },
191
- );
192
-
193
- // Log tab events globally for debugging
194
- document.addEventListener("keydown", (e) => {
195
- if (e.key === "Tab") {
196
- console.log("Global tab event:", {
197
- target: e.target,
198
- shiftKey: e.shiftKey,
199
- defaultPrevented: e.defaultPrevented,
200
- activeElement: document.activeElement,
201
- });
202
- }
203
- });
204
- </script>
205
- </body>
206
- </html>