@jsenv/dom 0.1.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 (101) hide show
  1. package/dist/jsenv_dom.js +9653 -0
  2. package/index.js +101 -0
  3. package/package.json +47 -0
  4. package/src/attr/add_attribute_effect.js +93 -0
  5. package/src/attr/attributes.js +32 -0
  6. package/src/demos/3_columns_resize_demo.html +84 -0
  7. package/src/demos/3_rows_resize_demo.html +89 -0
  8. package/src/demos/aside_and_main_demo.html +93 -0
  9. package/src/demos/coordinates_demo.html +450 -0
  10. package/src/demos/document_autoscroll_demo.html +517 -0
  11. package/src/demos/drag_gesture_constraints_demo.html +701 -0
  12. package/src/demos/drag_gesture_demo.html +1047 -0
  13. package/src/demos/drag_gesture_element_to_impact_demo.html +445 -0
  14. package/src/demos/drag_reference_element_demo.html +480 -0
  15. package/src/demos/flex_details_set_demo.html +302 -0
  16. package/src/demos/flex_details_set_demo_2.html +315 -0
  17. package/src/demos/visible_rect_demo.html +525 -0
  18. package/src/interaction/drag/constraint_feedback_line.js +92 -0
  19. package/src/interaction/drag/drag_constraint.js +659 -0
  20. package/src/interaction/drag/drag_debug_markers.js +635 -0
  21. package/src/interaction/drag/drag_element_positioner.js +382 -0
  22. package/src/interaction/drag/drag_gesture.js +566 -0
  23. package/src/interaction/drag/drag_resize_demo.html +571 -0
  24. package/src/interaction/drag/drag_to_move.js +301 -0
  25. package/src/interaction/drag/drag_to_resize_gesture.js +68 -0
  26. package/src/interaction/drag/drop_target_detection.js +148 -0
  27. package/src/interaction/drag/sticky_frontiers.js +160 -0
  28. package/src/interaction/element_log.js +8 -0
  29. package/src/interaction/event_marker.js +14 -0
  30. package/src/interaction/focus/active_element.js +33 -0
  31. package/src/interaction/focus/arrow_navigation.js +599 -0
  32. package/src/interaction/focus/element_is_focusable.js +57 -0
  33. package/src/interaction/focus/element_is_visible.js +36 -0
  34. package/src/interaction/focus/find_focusable.js +21 -0
  35. package/src/interaction/focus/focus_group.js +91 -0
  36. package/src/interaction/focus/focus_group_registry.js +12 -0
  37. package/src/interaction/focus/focus_nav.js +12 -0
  38. package/src/interaction/focus/focus_nav_event_marker.js +14 -0
  39. package/src/interaction/focus/focus_trap.js +105 -0
  40. package/src/interaction/focus/tab_navigation.js +128 -0
  41. package/src/interaction/focus/tests/focus_group_skip_tab_test.html +206 -0
  42. package/src/interaction/focus/tests/tree_focus_test.html +304 -0
  43. package/src/interaction/focus/tests/tree_focus_test.jsx +261 -0
  44. package/src/interaction/focus/tests/tree_focus_test_preact.html +13 -0
  45. package/src/interaction/isolate_interactions.js +161 -0
  46. package/src/interaction/keyboard.js +26 -0
  47. package/src/interaction/scroll/capture_scroll.js +47 -0
  48. package/src/interaction/scroll/is_scrollable.js +159 -0
  49. package/src/interaction/scroll/scroll_container.js +110 -0
  50. package/src/interaction/scroll/scroll_trap.js +44 -0
  51. package/src/interaction/scroll/scrollbar_size.js +20 -0
  52. package/src/interaction/scroll/wheel_through.js +138 -0
  53. package/src/iterable_weak_set.js +66 -0
  54. package/src/position/dom_coords.js +340 -0
  55. package/src/position/offset_parent.js +15 -0
  56. package/src/position/position_fixed.js +15 -0
  57. package/src/position/position_sticky.js +213 -0
  58. package/src/position/sticky_rect.js +79 -0
  59. package/src/position/visible_rect.js +482 -0
  60. package/src/pub_sub.js +28 -0
  61. package/src/size/can_take_size.js +11 -0
  62. package/src/size/details_content_full_height.js +63 -0
  63. package/src/size/flex_details_set.js +974 -0
  64. package/src/size/get_available_height.js +22 -0
  65. package/src/size/get_available_width.js +22 -0
  66. package/src/size/get_border_sizes.js +14 -0
  67. package/src/size/get_height.js +4 -0
  68. package/src/size/get_inner_height.js +15 -0
  69. package/src/size/get_inner_width.js +15 -0
  70. package/src/size/get_margin_sizes.js +10 -0
  71. package/src/size/get_max_height.js +57 -0
  72. package/src/size/get_max_width.js +47 -0
  73. package/src/size/get_min_height.js +14 -0
  74. package/src/size/get_min_width.js +14 -0
  75. package/src/size/get_padding_sizes.js +10 -0
  76. package/src/size/get_width.js +4 -0
  77. package/src/size/hooks/use_available_height.js +27 -0
  78. package/src/size/hooks/use_available_width.js +27 -0
  79. package/src/size/hooks/use_max_height.js +10 -0
  80. package/src/size/hooks/use_max_width.js +10 -0
  81. package/src/size/hooks/use_resize_status.js +62 -0
  82. package/src/size/resize.js +695 -0
  83. package/src/size/resolve_css_size.js +32 -0
  84. package/src/style/dom_styles.js +97 -0
  85. package/src/style/style_composition.js +78 -0
  86. package/src/style/style_controller.js +345 -0
  87. package/src/style/style_parsing.js +317 -0
  88. package/src/transition/demos/animation_resumption_test.xhtml +500 -0
  89. package/src/transition/demos/height_toggle_test.xhtml +515 -0
  90. package/src/transition/dom_transition.js +254 -0
  91. package/src/transition/easing.js +48 -0
  92. package/src/transition/group_transition.js +261 -0
  93. package/src/transition/transform_style_parser.js +32 -0
  94. package/src/transition/transition_playback.js +366 -0
  95. package/src/transition/transition_timeline.js +79 -0
  96. package/src/traversal.js +247 -0
  97. package/src/ui_transition/demos/content_states_transition_demo.html +628 -0
  98. package/src/ui_transition/demos/smooth_height_transition_demo.html +149 -0
  99. package/src/ui_transition/demos/transition_testing.html +354 -0
  100. package/src/ui_transition/ui_transition.js +1492 -0
  101. package/src/utils.js +69 -0
package/index.js ADDED
@@ -0,0 +1,101 @@
1
+ // state management
2
+ export { createIterableWeakSet } from "./src/iterable_weak_set.js";
3
+ export { createPubSub } from "./src/pub_sub.js";
4
+
5
+ // style
6
+ export { addWillChange, getStyle, setStyles } from "./src/style/dom_styles.js";
7
+ export { createStyleController } from "./src/style/style_controller.js";
8
+
9
+ // attributes
10
+ export { addAttributeEffect } from "./src/attr/add_attribute_effect.js";
11
+ export { setAttribute, setAttributes } from "./src/attr/attributes.js";
12
+
13
+ // traversal
14
+ export {
15
+ findAfter,
16
+ findAncestor,
17
+ findBefore,
18
+ findDescendant,
19
+ } from "./src/traversal.js";
20
+
21
+ // interaction/focus
22
+ export {
23
+ activeElementSignal,
24
+ addActiveElementEffect,
25
+ useActiveElement,
26
+ } from "./src/interaction/focus/active_element.js";
27
+ export { elementIsFocusable } from "./src/interaction/focus/element_is_focusable.js";
28
+ export { elementIsVisible } from "./src/interaction/focus/element_is_visible.js";
29
+ export { findFocusable } from "./src/interaction/focus/find_focusable.js";
30
+ export { initFocusGroup } from "./src/interaction/focus/focus_group.js";
31
+ export { preventFocusNavViaKeyboard } from "./src/interaction/focus/focus_nav.js";
32
+ export { preventFocusNav } from "./src/interaction/focus/focus_nav_event_marker.js";
33
+ export { trapFocusInside } from "./src/interaction/focus/focus_trap.js";
34
+ // interaction/keyboard
35
+ export { canInterceptKeys } from "./src/interaction/keyboard.js";
36
+ // interaction/scroll
37
+ export { captureScrollState } from "./src/interaction/scroll/capture_scroll.js";
38
+ export { isScrollable } from "./src/interaction/scroll/is_scrollable.js";
39
+ export {
40
+ getScrollContainer,
41
+ getScrollContainerSet,
42
+ getSelfAndAncestorScrolls,
43
+ } from "./src/interaction/scroll/scroll_container.js";
44
+ export { trapScrollInside } from "./src/interaction/scroll/scroll_trap.js";
45
+ export { allowWheelThrough } from "./src/interaction/scroll/wheel_through.js";
46
+ // interaction/drag
47
+ export { getDragCoordinates } from "./src/interaction/drag/drag_element_positioner.js";
48
+ export {
49
+ createDragGestureController,
50
+ dragAfterThreshold,
51
+ } from "./src/interaction/drag/drag_gesture.js";
52
+ export { createDragToMoveGestureController } from "./src/interaction/drag/drag_to_move.js";
53
+ export { startDragToResizeGesture } from "./src/interaction/drag/drag_to_resize_gesture.js";
54
+ export { getDropTargetInfo } from "./src/interaction/drag/drop_target_detection.js";
55
+
56
+ // position
57
+ export { getScrollRelativeRect } from "./src/position/dom_coords.js";
58
+ export { getPositionedParent } from "./src/position/offset_parent.js";
59
+ export { initPositionSticky } from "./src/position/position_sticky.js";
60
+ export { stickyAsRelativeCoords } from "./src/position/sticky_rect.js";
61
+ export {
62
+ pickPositionRelativeTo,
63
+ visibleRectEffect,
64
+ } from "./src/position/visible_rect.js";
65
+
66
+ // size
67
+ export { initFlexDetailsSet } from "./src/size/flex_details_set.js";
68
+ export { getAvailableHeight } from "./src/size/get_available_height.js";
69
+ export { getAvailableWidth } from "./src/size/get_available_width.js";
70
+ export { getBorderSizes } from "./src/size/get_border_sizes.js";
71
+ export { getHeight } from "./src/size/get_height.js";
72
+ export { getInnerHeight } from "./src/size/get_inner_height.js";
73
+ export { getInnerWidth } from "./src/size/get_inner_width.js";
74
+ export { getMarginSizes } from "./src/size/get_margin_sizes.js";
75
+ export { getMaxHeight } from "./src/size/get_max_height.js";
76
+ export { getMaxWidth } from "./src/size/get_max_width.js";
77
+ export { getMinHeight } from "./src/size/get_min_height.js";
78
+ export { getMinWidth } from "./src/size/get_min_width.js";
79
+ export { getPaddingSizes } from "./src/size/get_padding_sizes.js";
80
+ export { getWidth } from "./src/size/get_width.js";
81
+ export { resolveCSSSize } from "./src/size/resolve_css_size.js";
82
+ // size hooks
83
+ export { useAvailableHeight } from "./src/size/hooks/use_available_height.js";
84
+ export { useAvailableWidth } from "./src/size/hooks/use_available_width.js";
85
+ export { useMaxHeight } from "./src/size/hooks/use_max_height.js";
86
+ export { useMaxWidth } from "./src/size/hooks/use_max_width.js";
87
+ export { useResizeStatus } from "./src/size/hooks/use_resize_status.js";
88
+
89
+ // transition
90
+ export {
91
+ createHeightTransition,
92
+ createOpacityTransition,
93
+ createTranslateXTransition,
94
+ createWidthTransition,
95
+ } from "./src/transition/dom_transition.js";
96
+ export { EASING, cubicBezier } from "./src/transition/easing.js";
97
+ export {
98
+ createTimelineTransition,
99
+ createTransition,
100
+ } from "./src/transition/transition_playback.js";
101
+ export { initUITransition } from "./src/ui_transition/ui_transition.js";
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@jsenv/dom",
3
+ "version": "0.1.0",
4
+ "description": "DOM utilities for writing frontend code",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/jsenv/core",
8
+ "directory": "packages/frontend/dom"
9
+ },
10
+ "license": "MIT",
11
+ "author": {
12
+ "name": "dmail",
13
+ "email": "dmaillard06@gmail.com"
14
+ },
15
+ "sideEffects": [
16
+ "./dist/jsenv_dom.js"
17
+ ],
18
+ "type": "module",
19
+ "exports": {
20
+ ".": {
21
+ "dev:jsenv": "./index.js",
22
+ "default": "./dist/jsenv_dom.js"
23
+ },
24
+ "./details_content_full_height": "./src/size/details_content_full_height.js",
25
+ "./details_toggle_animation": "./src/details_toggle_animation.js",
26
+ "./resize": "./src/size/resize.js"
27
+ },
28
+ "files": [
29
+ "/dist/",
30
+ "/src/",
31
+ "/index.js"
32
+ ],
33
+ "scripts": {
34
+ "build": "node ./scripts/build.mjs",
35
+ "prepublishOnly": "npm run build"
36
+ },
37
+ "dependencies": {},
38
+ "devDependencies": {
39
+ "@jsenv/core": "../../../",
40
+ "@jsenv/navi": "../navi",
41
+ "@preact/signals": "^2.3.2",
42
+ "preact": "^10.27.2"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }
@@ -0,0 +1,93 @@
1
+ export const addAttributeEffect = (attributeName, effect) => {
2
+ const cleanupWeakMap = new WeakMap();
3
+ const applyEffect = (element) => {
4
+ const cleanup = effect(element);
5
+ cleanupWeakMap.set(
6
+ element,
7
+ typeof cleanup === "function" ? cleanup : () => {},
8
+ );
9
+ };
10
+
11
+ const cleanupEffect = (element) => {
12
+ const cleanup = cleanupWeakMap.get(element);
13
+ if (cleanup) {
14
+ cleanup();
15
+ cleanupWeakMap.delete(element);
16
+ }
17
+ };
18
+
19
+ const checkElement = (element) => {
20
+ if (element.hasAttribute(attributeName)) {
21
+ applyEffect(element);
22
+ }
23
+ const elementWithAttributeCollection = element.querySelectorAll(
24
+ `[${attributeName}]`,
25
+ );
26
+ for (const elementWithAttribute of elementWithAttributeCollection) {
27
+ applyEffect(elementWithAttribute);
28
+ }
29
+ };
30
+
31
+ checkElement(document.body);
32
+ const mutationObserver = new MutationObserver((mutations) => {
33
+ for (const mutation of mutations) {
34
+ if (mutation.type === "childList") {
35
+ for (const addedNode of mutation.addedNodes) {
36
+ if (addedNode.nodeType !== Node.ELEMENT_NODE) {
37
+ continue;
38
+ }
39
+ checkElement(addedNode);
40
+ }
41
+
42
+ for (const removedNode of mutation.removedNodes) {
43
+ if (removedNode.nodeType !== Node.ELEMENT_NODE) {
44
+ continue;
45
+ }
46
+
47
+ // Clean up the removed node itself if it had the attribute
48
+ if (
49
+ removedNode.hasAttribute &&
50
+ removedNode.hasAttribute(attributeName)
51
+ ) {
52
+ cleanupEffect(removedNode);
53
+ }
54
+
55
+ // Clean up any children of the removed node that had the attribute
56
+ if (removedNode.querySelectorAll) {
57
+ const elementsWithAttribute = removedNode.querySelectorAll(
58
+ `[${attributeName}]`,
59
+ );
60
+ for (const element of elementsWithAttribute) {
61
+ cleanupEffect(element);
62
+ }
63
+ }
64
+ }
65
+ }
66
+ if (
67
+ mutation.type === "attributes" &&
68
+ mutation.attributeName === attributeName
69
+ ) {
70
+ const element = mutation.target;
71
+ if (element.hasAttribute(attributeName)) {
72
+ applyEffect(element);
73
+ } else {
74
+ cleanupEffect(element);
75
+ }
76
+ }
77
+ }
78
+ });
79
+ mutationObserver.observe(document.body, {
80
+ childList: true,
81
+ subtree: true,
82
+ attributes: true,
83
+ attributeFilter: [attributeName],
84
+ });
85
+
86
+ return () => {
87
+ mutationObserver.disconnect();
88
+ for (const cleanup of cleanupWeakMap.values()) {
89
+ cleanup();
90
+ }
91
+ cleanupWeakMap.clear();
92
+ };
93
+ };
@@ -0,0 +1,32 @@
1
+ export const setAttribute = (element, name, value) => {
2
+ if (element.hasAttribute(name)) {
3
+ const prevValue = element.getAttribute(name);
4
+ element.setAttribute(name, value);
5
+ return () => {
6
+ element.setAttribute(name, prevValue);
7
+ };
8
+ }
9
+ element.setAttribute(name, value);
10
+ return () => {
11
+ element.removeAttribute(name);
12
+ };
13
+ };
14
+
15
+ const createSetMany = (setter) => {
16
+ return (element, description) => {
17
+ const cleanupCallbackSet = new Set();
18
+ for (const name of Object.keys(description)) {
19
+ const value = description[name];
20
+ const restoreStyle = setter(element, name, value);
21
+ cleanupCallbackSet.add(restoreStyle);
22
+ }
23
+ return () => {
24
+ for (const cleanupCallback of cleanupCallbackSet) {
25
+ cleanupCallback();
26
+ }
27
+ cleanupCallbackSet.clear();
28
+ };
29
+ };
30
+ };
31
+
32
+ export const setAttributes = createSetMany(setAttribute);
@@ -0,0 +1,84 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>3 columns resize x demo</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta charset="utf-8" />
7
+ <link rel="icon" href="data:," />
8
+ </head>
9
+ <body>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ }
14
+
15
+ * {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ #app {
20
+ display: flex;
21
+ flex-direction: row;
22
+ width: 600px;
23
+ border: 1px solid black;
24
+ box-sizing: content-box;
25
+ }
26
+
27
+ [data-resize-handle] {
28
+ flex-grow: 1;
29
+ min-width: 50px;
30
+ }
31
+ </style>
32
+
33
+ <div id="app">
34
+ <div
35
+ id="a"
36
+ data-resize="horizontal"
37
+ data-resize-handle
38
+ style="background: blue; height: 100px"
39
+ ></div>
40
+ <div
41
+ id="b"
42
+ data-resize="horizontal"
43
+ data-resize-handle
44
+ style="background: red; height: 200px"
45
+ ></div>
46
+ <div
47
+ id="c"
48
+ data-resize="horizontal"
49
+ data-resize-handle
50
+ style="background: green; height: 100px"
51
+ ></div>
52
+ </div>
53
+
54
+ <label>
55
+ width:
56
+ <input type="number" id="container_width" />
57
+ </label>
58
+
59
+ <script type="module">
60
+ import "@jsenv/dom/resize";
61
+
62
+ const resizeCollection = document.querySelectorAll("[data-resize]");
63
+ for (const resize of resizeCollection) {
64
+ const sizeInLocalStorage = localStorage.getItem(`${resize.id}_width`);
65
+ if (sizeInLocalStorage) {
66
+ resize.style.width = `${sizeInLocalStorage}px`;
67
+ }
68
+ resize.addEventListener("resize", (event) => {
69
+ console.log("resize event", event.target.id, resize.id);
70
+ localStorage.setItem(`${resize.id}_width`, event.detail.width);
71
+ });
72
+ }
73
+
74
+ const input = document.querySelector("input");
75
+ input.oninput = () => {
76
+ const width = input.value;
77
+ document.querySelector("#app").style.width = `${width}px`;
78
+ };
79
+ input.value = document
80
+ .querySelector("#app")
81
+ .getBoundingClientRect().width;
82
+ </script>
83
+ </body>
84
+ </html>
@@ -0,0 +1,89 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>3 rows resize x demo</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta charset="utf-8" />
7
+ <link rel="icon" href="data:," />
8
+ </head>
9
+ <body>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ }
14
+
15
+ * {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ #app {
20
+ display: flex;
21
+ flex-direction: column;
22
+ height: 600px;
23
+ border: 1px solid black;
24
+ box-sizing: content-box;
25
+ width: 300px;
26
+ margin-left: 200px;
27
+ margin-top: 50px;
28
+ }
29
+
30
+ [data-resize-handle] {
31
+ flex-grow: 1;
32
+ min-height: 50px;
33
+ }
34
+ </style>
35
+
36
+ <div id="app">
37
+ <div
38
+ id="a"
39
+ data-resize="vertical"
40
+ data-resize-handle
41
+ data-resize-absolute
42
+ style="background: blue; width: 100px"
43
+ ></div>
44
+ <div
45
+ id="b"
46
+ data-resize="vertical"
47
+ data-resize-handle
48
+ data-resize-absolute
49
+ style="background: red; width: 200px"
50
+ ></div>
51
+ <div
52
+ id="c"
53
+ data-resize="vertical"
54
+ data-resize-handle
55
+ data-resize-absolute
56
+ style="background: green; width: 100px"
57
+ ></div>
58
+ </div>
59
+
60
+ <label>
61
+ height:
62
+ <input type="number" id="container_size" />
63
+ </label>
64
+
65
+ <script type="module">
66
+ import "@jsenv/dom/resize";
67
+
68
+ const resizeCollection = document.querySelectorAll("[data-resize]");
69
+ for (const resize of resizeCollection) {
70
+ const sizeInLocalStorage = localStorage.getItem(`${resize.id}_height`);
71
+ if (sizeInLocalStorage) {
72
+ resize.style.height = `${sizeInLocalStorage}px`;
73
+ }
74
+ resize.addEventListener("resize", (event) => {
75
+ localStorage.setItem(`${resize.id}_height`, event.detail.height);
76
+ });
77
+ }
78
+
79
+ const input = document.querySelector("input");
80
+ input.oninput = () => {
81
+ const size = input.value;
82
+ document.querySelector("#app").style.height = `${size}px`;
83
+ };
84
+ input.value = document
85
+ .querySelector("#app")
86
+ .getBoundingClientRect().height;
87
+ </script>
88
+ </body>
89
+ </html>
@@ -0,0 +1,93 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Aside and main demo</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta charset="utf-8" />
7
+ <link rel="icon" href="data:," />
8
+ </head>
9
+ <body>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ }
14
+
15
+ * {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ #app {
20
+ display: flex;
21
+ flex-direction: row;
22
+ }
23
+
24
+ aside {
25
+ background: #f0f0f0;
26
+ border-right: 1px solid #ccc;
27
+ height: 100vh;
28
+ position: sticky;
29
+ top: 0;
30
+ width: 200px;
31
+ min-width: 100px;
32
+ }
33
+
34
+ main {
35
+ min-width: 200px;
36
+ padding-bottom: 0;
37
+ min-width: 200px; /* Prevents content area from becoming too narrow */
38
+ box-sizing: border-box;
39
+ min-height: 100vh; /* Ensures content area is at least viewport height */
40
+ overflow-x: auto; /* Horizontal scrollbar appears when content is wider than available space */
41
+ scrollbar-gutter: stable; /* Reserves space for scrollbar to prevent layout shifts */
42
+ flex: 1;
43
+ }
44
+
45
+ .main_body {
46
+ width: fit-content;
47
+ padding: 20px; /* Consistent padding around main content */
48
+ }
49
+
50
+ aside > [data-resize-handle] {
51
+ position: absolute;
52
+ z-index: 1; /* Ensures resize handle appears above sidebar content */
53
+ width: 5px;
54
+ right: -2.5px; /* Centers the handle on the border */
55
+ top: 0;
56
+ bottom: 0;
57
+ cursor: ew-resize; /* Indicates horizontal resize capability */
58
+ }
59
+ aside > [data-resize-handle]:hover,
60
+ aside[data-resizing] > [data-resize-handle] {
61
+ background-color: blue;
62
+ opacity: 0.5;
63
+ }
64
+ </style>
65
+
66
+ <div id="app">
67
+ <aside data-resize="horizontal" data-resize-absolute>
68
+ <div data-resize-handle></div>
69
+ </aside>
70
+ <main>
71
+ <div class="main_body">
72
+ <h1 title="Explore and manager your database">Database Manager</h1>
73
+
74
+ <div style="background: blue; width: 600px; height: 600px"></div>
75
+ </div>
76
+ </main>
77
+ </div>
78
+
79
+ <script type="module">
80
+ import "@jsenv/dom/resize";
81
+
82
+ const aside = document.querySelector("aside");
83
+ const widthInLocalStorage = localStorage.getItem("sidebar_width");
84
+ if (widthInLocalStorage) {
85
+ aside.style.width = `${widthInLocalStorage}px`;
86
+ }
87
+
88
+ aside.addEventListener("resizeend", (event) => {
89
+ localStorage.setItem("sidebar_width", event.detail.width);
90
+ });
91
+ </script>
92
+ </body>
93
+ </html>