@jsenv/navi 0.2.0 → 0.3.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.
@@ -1,17 +1,17 @@
1
1
  <!doctype html>
2
2
  <html>
3
3
  <head>
4
- <title>Simple Validation Message Demo</title>
4
+ <title>Simple Callout Demo</title>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <meta charset="utf-8" />
7
7
  <link rel="icon" href="data:," />
8
8
  <style>
9
9
  body {
10
+ min-height: 300vh; /* Make page scrollable */
10
11
  margin: 0;
11
12
  padding: 20px;
12
13
  font-family: Arial, sans-serif;
13
14
  background: linear-gradient(45deg, #f0f8ff, #e6f3ff);
14
- min-height: 300vh; /* Make page scrollable */
15
15
  }
16
16
 
17
17
  .container {
@@ -29,14 +29,14 @@
29
29
 
30
30
  .target-element {
31
31
  display: inline-block;
32
+ margin: 10px;
32
33
  padding: 12px 20px;
33
- background: #4caf50;
34
34
  color: white;
35
+ font-size: 16px;
36
+ background: #4caf50;
35
37
  border: none;
36
38
  border-radius: 4px;
37
39
  cursor: pointer;
38
- margin: 10px;
39
- font-size: 16px;
40
40
  }
41
41
 
42
42
  .target-element:hover {
@@ -44,7 +44,14 @@
44
44
  }
45
45
 
46
46
  .spacer {
47
+ display: flex;
47
48
  height: 200px;
49
+ margin: 40px 0;
50
+ align-items: center;
51
+ justify-content: center;
52
+ color: #666;
53
+ font-weight: bold;
54
+ font-size: 18px;
48
55
  background: repeating-linear-gradient(
49
56
  45deg,
50
57
  transparent,
@@ -52,44 +59,37 @@
52
59
  rgba(0, 0, 0, 0.1) 20px,
53
60
  rgba(0, 0, 0, 0.1) 40px
54
61
  );
55
- margin: 40px 0;
56
- display: flex;
57
- align-items: center;
58
- justify-content: center;
59
- color: #666;
60
- font-weight: bold;
61
- font-size: 18px;
62
62
  }
63
63
 
64
64
  .scroll-instruction {
65
65
  position: fixed;
66
66
  top: 10px;
67
67
  right: 10px;
68
- background: #333;
69
- color: white;
68
+ z-index: 1000;
70
69
  padding: 10px;
71
- border-radius: 4px;
70
+ color: white;
72
71
  font-size: 14px;
73
- z-index: 1000;
72
+ background: #333;
73
+ border-radius: 4px;
74
74
  }
75
75
 
76
76
  .scrollable-container {
77
- height: 300px;
78
77
  width: 100%;
79
- overflow: auto;
78
+ height: 300px;
79
+ padding: 20px;
80
+ background: #f9f9f9;
80
81
  border: 2px solid #ddd;
81
82
  border-radius: 8px;
82
- background: #f9f9f9;
83
- padding: 20px;
83
+ overflow: auto;
84
84
  }
85
85
 
86
86
  .scrollable-content {
87
- height: 600px;
88
- width: 800px;
89
87
  display: flex;
88
+ width: 800px;
89
+ height: 600px;
90
90
  flex-direction: column;
91
- justify-content: space-around;
92
91
  align-items: center;
92
+ justify-content: space-around;
93
93
  background: repeating-linear-gradient(
94
94
  45deg,
95
95
  transparent,
@@ -101,20 +101,18 @@
101
101
  </style>
102
102
  </head>
103
103
  <body>
104
- <div class="scroll-instruction">
105
- Scroll to test validation message positioning
106
- </div>
104
+ <div class="scroll-instruction">Scroll to test callout positioning</div>
107
105
 
108
106
  <div class="container">
109
- <h1>Simple Validation Message Demo</h1>
107
+ <h1>Simple Callout Demo</h1>
110
108
  <p>
111
- This demo shows how the validation message follows its target element
112
- during scrolling.
109
+ This demo shows how the callout follows its target element during
110
+ scrolling.
113
111
  </p>
114
112
 
115
113
  <div class="section">
116
- <h2>Auto-opened Validation Message</h2>
117
- <p>This validation message opens automatically when the page loads:</p>
114
+ <h2>Auto-opened Callout</h2>
115
+ <p>This callout opens automatically when the page loads:</p>
118
116
  <p>
119
117
  <strong
120
118
  >Scroll inside the container below to test positioning within a
@@ -139,8 +137,8 @@
139
137
  <div class="section">
140
138
  <h2>Middle Section</h2>
141
139
  <p>
142
- The validation message should follow the target element as you scroll
143
- past this section.
140
+ The callout should follow the target element as you scroll past this
141
+ section.
144
142
  </p>
145
143
  </div>
146
144
 
@@ -158,7 +156,7 @@
158
156
 
159
157
  <div class="section">
160
158
  <h2>Final Section</h2>
161
- <p>Scroll back up to see how the validation message behaves.</p>
159
+ <p>Scroll back up to see how the callout behaves.</p>
162
160
  </div>
163
161
 
164
162
  <div
@@ -171,32 +169,32 @@
171
169
  margin-top: 40px;
172
170
  "
173
171
  >
174
- <p>Bottom spacer - scroll back up to see validation messages</p>
172
+ <p>Bottom spacer - scroll back up to see callouts</p>
175
173
  </div>
176
174
  </div>
177
175
 
178
176
  <script type="module">
179
- import { openValidationMessage } from "../validation_message.js";
177
+ import { openCallout } from "@jsenv/navi";
180
178
 
181
- // Auto-open validation message on page load
179
+ // Auto-open callout on page load
182
180
  window.addEventListener("load", () => {
183
181
  const autoTarget = document.getElementById("auto-target");
184
- openValidationMessage(
185
- autoTarget,
186
- `This is a very long validation message that was opened automatically when the page loaded. <strong>Try scrolling both horizontally and vertically</strong> within the container to see how the validation message follows the target element! This message is intentionally made very long to test how the positioning system handles messages that exceed the visible area of their container. The message should properly position itself even when parts of it would normally be clipped by the container boundaries. You can scroll in any direction - up, down, left, right - to test the robustness of the positioning algorithm. This lengthy content helps verify that the validation message system can handle edge cases where the message content is larger than the available viewport space within scrollable containers.`,
182
+ openCallout(
183
+ `This is a very long callout that was opened automatically when the page loaded. <strong>Try scrolling both horizontally and vertically</strong> within the container to see how the callout follows the target element! This message is intentionally made very long to test how the positioning system handles callouts that exceed the visible area of their container. The callout should properly position itself even when parts of it would normally be clipped by the container boundaries. You can scroll in any direction - up, down, left, right - to test the robustness of the positioning algorithm. This lengthy content helps verify that the callout system can handle edge cases where the callout content is larger than the available viewport space within scrollable containers.`,
187
184
  {
185
+ anchorElement: autoTarget,
188
186
  level: "warning",
189
187
  debug: true,
190
188
  canClickThrough: true,
191
189
  onClose: () => {
192
- console.log("Validation message was closed");
190
+ console.log("Callout was closed");
193
191
  },
194
192
  },
195
193
  );
196
194
  });
197
195
 
198
196
  console.log(
199
- "Simple validation message demo loaded. Check console for debug output.",
197
+ "Simple callout demo loaded. Check console for debug output.",
200
198
  );
201
199
  </script>
202
200
  </body>
@@ -0,0 +1,161 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Dynamic Callout Positioning Test</title>
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ padding: 20px;
10
+ font-family: Arial, sans-serif;
11
+ }
12
+ .anchor {
13
+ display: inline-block;
14
+ margin: 10px;
15
+ padding: 10px 20px;
16
+ color: white;
17
+ background: #007acc;
18
+ border-radius: 4px;
19
+ cursor: pointer;
20
+ }
21
+ .test-section {
22
+ margin: 40px 0;
23
+ padding: 20px;
24
+ border: 1px solid #ddd;
25
+ border-radius: 4px;
26
+ }
27
+ .resize-controls {
28
+ position: fixed;
29
+ top: 10px;
30
+ right: 10px;
31
+ z-index: 1000;
32
+ padding: 10px;
33
+ background: white;
34
+ border: 1px solid #ccc;
35
+ border-radius: 4px;
36
+ }
37
+ .large-anchor {
38
+ display: flex;
39
+ width: 300px;
40
+ height: 200px;
41
+ margin: 20px;
42
+ align-items: center;
43
+ justify-content: center;
44
+ background: #28a745;
45
+ }
46
+ .tiny-anchor {
47
+ display: flex;
48
+ width: 50px;
49
+ height: 20px;
50
+ margin: 20px;
51
+ align-items: center;
52
+ justify-content: center;
53
+ font-size: 12px;
54
+ background: #dc3545;
55
+ }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="resize-controls">
60
+ <button onclick="resizeWindow(800, 600)">Small (800x600)</button>
61
+ <button onclick="resizeWindow(1200, 800)">Medium (1200x800)</button>
62
+ <button onclick="resizeWindow(1600, 1000)">Large (1600x1000)</button>
63
+ <div>Current: <span id="current-size"></span></div>
64
+ </div>
65
+
66
+ <div class="test-section">
67
+ <h2>Test 1: Normal anchor element</h2>
68
+ <div
69
+ class="anchor"
70
+ id="anchor1"
71
+ onclick="openTestCallout(this, 'Normal anchor test')"
72
+ >
73
+ Click me - Normal anchor
74
+ </div>
75
+ <p>
76
+ This should show an anchored callout with arrow when viewport is large
77
+ enough.
78
+ </p>
79
+ </div>
80
+
81
+ <div class="test-section">
82
+ <h2>Test 2: Large anchor element</h2>
83
+ <div
84
+ class="large-anchor"
85
+ id="anchor2"
86
+ onclick="openTestCallout(this, 'Large anchor test - should be centered when too big for viewport')"
87
+ >
88
+ Large Anchor Element
89
+ </div>
90
+ <p>
91
+ This should center in viewport when anchor is too large relative to
92
+ viewport height.
93
+ </p>
94
+ </div>
95
+
96
+ <div class="test-section">
97
+ <h2>Test 3: Tiny anchor element</h2>
98
+ <div
99
+ class="tiny-anchor"
100
+ id="anchor3"
101
+ onclick="openTestCallout(this, 'Tiny anchor test')"
102
+ >
103
+ Tiny
104
+ </div>
105
+ <p>This should work with anchored positioning in most cases.</p>
106
+ </div>
107
+
108
+ <script type="module">
109
+ import { openCallout } from "./callout.js";
110
+
111
+ let currentCallout = null;
112
+
113
+ window.openTestCallout = (anchorElement, message) => {
114
+ // Close existing callout
115
+ if (currentCallout) {
116
+ currentCallout.close();
117
+ }
118
+
119
+ currentCallout = openCallout({
120
+ anchorElement,
121
+ calloutElement: createCalloutContent(message),
122
+ level: "info",
123
+ debug: true,
124
+ });
125
+ };
126
+
127
+ function createCalloutContent(message) {
128
+ const div = document.createElement("div");
129
+ div.style.padding = "20px";
130
+ div.style.maxWidth = "300px";
131
+ div.style.background = "white";
132
+ div.innerHTML = `
133
+ <h3>Dynamic Positioning Test</h3>
134
+ <p>${message}</p>
135
+ <p>Window size: ${window.innerWidth} x ${window.innerHeight}</p>
136
+ <button onclick="currentCallout?.close()">Close</button>
137
+ `;
138
+ return div;
139
+ }
140
+
141
+ window.resizeWindow = (width, height) => {
142
+ window.resizeTo(width, height);
143
+ updateSizeDisplay();
144
+ };
145
+
146
+ function updateSizeDisplay() {
147
+ document.getElementById("current-size").textContent =
148
+ `${window.innerWidth} x ${window.innerHeight}`;
149
+ }
150
+
151
+ // Update size display on load and resize
152
+ window.addEventListener("load", updateSizeDisplay);
153
+ window.addEventListener("resize", updateSizeDisplay);
154
+
155
+ // Test automatic strategy changes on resize
156
+ window.addEventListener("resize", () => {
157
+ console.debug("Window resized, callout should re-evaluate strategy");
158
+ });
159
+ </script>
160
+ </body>
161
+ </html>
@@ -17,7 +17,6 @@ import { useExecuteAction } from "../action_execution/use_execute_action.js";
17
17
  import { LoaderBackground } from "../loader/loader_background.jsx";
18
18
  import { useAutoFocus } from "../use_auto_focus.js";
19
19
  import { initCustomField } from "./custom_field.js";
20
- import "./navi_css_vars.js";
21
20
  import { useActionEvents } from "./use_action_events.js";
22
21
  import { useFormEvents } from "./use_form_events.js";
23
22
  import {
@@ -38,140 +37,146 @@ import {
38
37
  * So we redefine chrome styles so that loader can keep up with the actual color visible to the user
39
38
  */
40
39
  import.meta.css = /* css */ `
41
- .navi_button {
42
- position: relative;
43
- display: inline-block;
44
- padding: 0;
45
- background: none;
46
- border: none;
47
- outline: none;
40
+ @layer navi {
41
+ .navi_button {
42
+ position: relative;
43
+ display: inline-block;
44
+ padding: 0;
45
+ background: none;
46
+ border: none;
47
+ outline: none;
48
48
 
49
- --border-width: 1px;
50
- --outline-width: 1px;
51
- --outer-width: calc(var(--border-width) + var(--outline-width));
52
- --padding-x: 6px;
53
- --padding-y: 1px;
49
+ --border-width: 1px;
50
+ --outline-width: 1px;
51
+ --outer-width: calc(var(--border-width) + var(--outline-width));
52
+ --padding-x: 6px;
53
+ --padding-y: 1px;
54
54
 
55
- --outline-color: light-dark(#4476ff, #3b82f6);
55
+ --outline-color: light-dark(#4476ff, #3b82f6);
56
56
 
57
- --border-radius: 2px;
58
- --border-color: light-dark(#767676, #8e8e93);
59
- --border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
60
- --border-color-active: color-mix(in srgb, var(--border-color) 90%, black);
61
- --border-color-readonly: color-mix(in srgb, var(--border-color) 30%, white);
62
- --border-color-disabled: var(--border-color-readonly);
57
+ --border-radius: 2px;
58
+ --border-color: light-dark(#767676, #8e8e93);
59
+ --border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
60
+ --border-color-active: color-mix(in srgb, var(--border-color) 90%, black);
61
+ --border-color-readonly: color-mix(
62
+ in srgb,
63
+ var(--border-color) 30%,
64
+ white
65
+ );
66
+ --border-color-disabled: var(--border-color-readonly);
63
67
 
64
- --background-color: light-dark(#f3f4f6, #2d3748);
65
- --background-color-hover: color-mix(
66
- in srgb,
67
- var(--background-color) 95%,
68
- black
69
- );
70
- --background-color-readonly: var(--background-color);
71
- --background-color-disabled: var(--background-color);
68
+ --background-color: light-dark(#f3f4f6, #2d3748);
69
+ --background-color-hover: color-mix(
70
+ in srgb,
71
+ var(--background-color) 95%,
72
+ black
73
+ );
74
+ --background-color-readonly: var(--background-color);
75
+ --background-color-disabled: var(--background-color);
72
76
 
73
- --color: currentColor;
74
- --color-readonly: color-mix(in srgb, currentColor 30%, transparent);
75
- --color-disabled: var(--color-readonly);
76
- }
77
- .navi_button_content {
78
- position: relative;
79
- display: inline-flex;
80
- padding-top: var(--padding-y);
81
- padding-right: var(--padding-x);
82
- padding-bottom: var(--padding-y);
83
- padding-left: var(--padding-x);
84
- color: var(--color);
85
- background-color: var(--background-color);
86
- border-width: var(--outer-width);
87
- border-style: solid;
88
- border-color: transparent;
89
- border-radius: var(--border-radius);
90
- outline-width: var(--border-width);
91
- outline-style: solid;
92
- outline-color: var(--border-color);
93
- outline-offset: calc(-1 * (var(--border-width)));
94
- transition-property: transform;
95
- transition-duration: 0.15s;
96
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
97
- }
98
- .navi_button_shadow {
99
- position: absolute;
100
- inset: calc(-1 * var(--outer-width));
101
- border-radius: inherit;
102
- pointer-events: none;
103
- }
104
- /* Focus */
105
- .navi_button[data-focus-visible] .navi_button_content {
106
- --border-color: var(--outline-color);
107
- outline-width: var(--outer-width);
108
- outline-offset: calc(-1 * var(--outer-width));
109
- }
110
- /* Hover */
111
- .navi_button[data-hover] .navi_button_content {
112
- --border-color: var(--border-color-hover);
113
- --background-color: var(--background-color-hover);
114
- }
115
- /* Active */
116
- .navi_button[data-active] .navi_button_content {
117
- --outline-color: var(--border-color-active);
118
- --background-color: none;
119
- transform: scale(0.9);
120
- }
121
- .navi_button[data-active] .navi_button_shadow {
122
- box-shadow:
123
- inset 0 3px 6px rgba(0, 0, 0, 0.2),
124
- inset 0 1px 2px rgba(0, 0, 0, 0.3),
125
- inset 0 0 0 1px rgba(0, 0, 0, 0.1),
126
- inset 2px 0 4px rgba(0, 0, 0, 0.1),
127
- inset -2px 0 4px rgba(0, 0, 0, 0.1);
128
- }
129
- /* Readonly */
130
- .navi_button[data-readonly] .navi_button_content {
131
- --border-color: var(--border-color-disabled);
132
- --outline-color: var(--border-color-readonly);
133
- --background-color: var(--background-color-readonly);
134
- --color: var(--color-readonly);
135
- }
136
- /* Disabled */
137
- .navi_button[data-disabled] .navi_button_content {
138
- --border-color: var(--border-color-disabled);
139
- --background-color: var(--background-color-disabled);
140
- --color: var(--color-disabled);
141
- transform: none; /* no active effect */
142
- }
143
- .navi_button[data-disabled] .navi_button_shadow {
144
- box-shadow: none;
145
- }
146
- /* Invalid */
147
- .navi_button[aria-invalid="true"] .navi_button_content {
148
- --border-color: var(--invalid-color);
149
- }
77
+ --color: currentColor;
78
+ --color-readonly: color-mix(in srgb, currentColor 30%, transparent);
79
+ --color-disabled: var(--color-readonly);
80
+ }
81
+ .navi_button_content {
82
+ position: relative;
83
+ display: inline-flex;
84
+ padding-top: var(--padding-y);
85
+ padding-right: var(--padding-x);
86
+ padding-bottom: var(--padding-y);
87
+ padding-left: var(--padding-x);
88
+ color: var(--color);
89
+ background-color: var(--background-color);
90
+ border-width: var(--outer-width);
91
+ border-style: solid;
92
+ border-color: transparent;
93
+ border-radius: var(--border-radius);
94
+ outline-width: var(--border-width);
95
+ outline-style: solid;
96
+ outline-color: var(--border-color);
97
+ outline-offset: calc(-1 * (var(--border-width)));
98
+ transition-property: transform;
99
+ transition-duration: 0.15s;
100
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
101
+ }
102
+ .navi_button_shadow {
103
+ position: absolute;
104
+ inset: calc(-1 * var(--outer-width));
105
+ border-radius: inherit;
106
+ pointer-events: none;
107
+ }
108
+ /* Focus */
109
+ .navi_button[data-focus-visible] .navi_button_content {
110
+ --border-color: var(--outline-color);
111
+ outline-width: var(--outer-width);
112
+ outline-offset: calc(-1 * var(--outer-width));
113
+ }
114
+ /* Hover */
115
+ .navi_button[data-hover] .navi_button_content {
116
+ --border-color: var(--border-color-hover);
117
+ --background-color: var(--background-color-hover);
118
+ }
119
+ /* Active */
120
+ .navi_button[data-active] .navi_button_content {
121
+ --outline-color: var(--border-color-active);
122
+ --background-color: none;
123
+ transform: scale(0.9);
124
+ }
125
+ .navi_button[data-active] .navi_button_shadow {
126
+ box-shadow:
127
+ inset 0 3px 6px rgba(0, 0, 0, 0.2),
128
+ inset 0 1px 2px rgba(0, 0, 0, 0.3),
129
+ inset 0 0 0 1px rgba(0, 0, 0, 0.1),
130
+ inset 2px 0 4px rgba(0, 0, 0, 0.1),
131
+ inset -2px 0 4px rgba(0, 0, 0, 0.1);
132
+ }
133
+ /* Readonly */
134
+ .navi_button[data-readonly] .navi_button_content {
135
+ --border-color: var(--border-color-disabled);
136
+ --outline-color: var(--border-color-readonly);
137
+ --background-color: var(--background-color-readonly);
138
+ --color: var(--color-readonly);
139
+ }
140
+ /* Disabled */
141
+ .navi_button[data-disabled] .navi_button_content {
142
+ --border-color: var(--border-color-disabled);
143
+ --background-color: var(--background-color-disabled);
144
+ --color: var(--color-disabled);
145
+ transform: none; /* no active effect */
146
+ }
147
+ .navi_button[data-disabled] .navi_button_shadow {
148
+ box-shadow: none;
149
+ }
150
+ /* Callout (info, warning, error) */
151
+ .navi_button[data-callout] .navi_button_content {
152
+ --border-color: var(--callout-color);
153
+ }
150
154
 
151
- /* Discrete variant */
152
- .navi_button[data-discrete] .navi_button_content {
153
- --background-color: transparent;
154
- --border-color: transparent;
155
- }
156
- .navi_button[data-discrete][data-hover] .navi_button_content {
157
- --border-color: var(--border-color-hover);
158
- }
159
- .navi_button[data-discrete][data-readonly] .navi_button_content {
160
- --border-color: transparent;
161
- }
162
- .navi_button[data-discrete][data-disabled] .navi_button_content {
163
- --border-color: transparent;
164
- }
165
- button[data-discrete] {
166
- background-color: transparent;
167
- border-color: transparent;
168
- }
169
- button[data-discrete]:hover {
170
- border-color: revert;
171
- }
172
- button[data-discrete][data-readonly],
173
- button[data-discrete][data-disabled] {
174
- border-color: transparent;
155
+ /* Discrete variant */
156
+ .navi_button[data-discrete] .navi_button_content {
157
+ --background-color: transparent;
158
+ --border-color: transparent;
159
+ }
160
+ .navi_button[data-discrete][data-hover] .navi_button_content {
161
+ --border-color: var(--border-color-hover);
162
+ }
163
+ .navi_button[data-discrete][data-readonly] .navi_button_content {
164
+ --border-color: transparent;
165
+ }
166
+ .navi_button[data-discrete][data-disabled] .navi_button_content {
167
+ --border-color: transparent;
168
+ }
169
+ button[data-discrete] {
170
+ background-color: transparent;
171
+ border-color: transparent;
172
+ }
173
+ button[data-discrete]:hover {
174
+ border-color: revert;
175
+ }
176
+ button[data-discrete][data-readonly],
177
+ button[data-discrete][data-disabled] {
178
+ border-color: transparent;
179
+ }
175
180
  }
176
181
  `;
177
182
  export const Button = forwardRef((props, ref) => {
@@ -225,7 +230,7 @@ const ButtonBasic = forwardRef((props, ref) => {
225
230
  data-readonly={innerReadOnly ? "" : undefined}
226
231
  data-readonly-silent={innerLoading ? "" : undefined}
227
232
  data-disabled={innerDisabled ? "" : undefined}
228
- data-validation-message-arrow-x="center"
233
+ data-callout-arrow-x="center"
229
234
  aria-busy={innerLoading}
230
235
  >
231
236
  <LoaderBackground
@@ -30,9 +30,11 @@ import {
30
30
  } from "./use_ui_state_controller.js";
31
31
 
32
32
  import.meta.css = /* css */ `
33
- .navi_checkbox_list {
34
- display: flex;
35
- flex-direction: column;
33
+ @layer navi {
34
+ .navi_checkbox_list {
35
+ display: flex;
36
+ flex-direction: column;
37
+ }
36
38
  }
37
39
  `;
38
40