@realmorg/ui 2.0.0-alpha.2 → 2.0.0-alpha.4

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 (65) hide show
  1. package/README.md +6 -6
  2. package/components/accordion.html +40 -1
  3. package/components/alert-dialog.html +40 -0
  4. package/components/alert.html +14 -0
  5. package/components/aspect-ratio.html +6 -0
  6. package/components/attachment.html +14 -0
  7. package/components/avatar.html +22 -0
  8. package/components/badge.html +6 -0
  9. package/components/breadcrumb.html +30 -0
  10. package/components/bubble.html +10 -0
  11. package/components/button-group.html +14 -0
  12. package/components/button.html +41 -22
  13. package/components/calendar.html +6 -0
  14. package/components/card.html +30 -0
  15. package/components/carousel.html +22 -0
  16. package/components/chart.html +6 -0
  17. package/components/checkbox.html +6 -0
  18. package/components/code-block.html +6 -0
  19. package/components/collapsible.html +14 -0
  20. package/components/combobox.html +26 -0
  21. package/components/command.html +12 -0
  22. package/components/context-menu.html +14 -0
  23. package/components/dialog.html +49 -3
  24. package/components/direction.html +6 -0
  25. package/components/drawer.html +46 -3
  26. package/components/dropdown-menu.html +75 -1
  27. package/components/empty.html +26 -0
  28. package/components/field.html +26 -0
  29. package/components/hover-card.html +41 -1
  30. package/components/input-group.html +16 -0
  31. package/components/input-otp.html +12 -0
  32. package/components/input.html +12 -0
  33. package/components/item.html +38 -0
  34. package/components/kbd.html +10 -0
  35. package/components/label.html +6 -0
  36. package/components/marker.html +6 -0
  37. package/components/menubar.html +36 -1
  38. package/components/message-scroller.html +6 -0
  39. package/components/message.html +18 -0
  40. package/components/navigation-menu.html +29 -0
  41. package/components/pagination.html +6 -0
  42. package/components/popover.html +53 -1
  43. package/components/progress.html +6 -0
  44. package/components/radio-group.html +6 -0
  45. package/components/resizable.html +14 -0
  46. package/components/scroll-area.html +6 -0
  47. package/components/select.html +12 -0
  48. package/components/separator.html +6 -0
  49. package/components/sheet.html +30 -0
  50. package/components/sidebar.html +52 -2
  51. package/components/skeleton.html +6 -0
  52. package/components/slider.html +28 -1
  53. package/components/spinner.html +6 -0
  54. package/components/switch.html +6 -0
  55. package/components/table.html +6 -0
  56. package/components/tabs.html +18 -0
  57. package/components/textarea.html +12 -0
  58. package/components/toaster.html +6 -0
  59. package/components/toggle-group.html +6 -0
  60. package/components/toggle.html +6 -0
  61. package/components/tooltip.html +43 -1
  62. package/dist/theme.css +13 -0
  63. package/dist/ui.html +1171 -37
  64. package/package.json +4 -3
  65. package/theme.css +13 -0
@@ -1,5 +1,11 @@
1
1
  <custom-element name="ui-card">
2
2
  <style>
3
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
4
+ touch-action does not reach shadow content in iOS Safari. */
5
+ :host,
6
+ * {
7
+ touch-action: manipulation;
8
+ }
3
9
  :host {
4
10
  display: flex;
5
11
  flex-direction: column;
@@ -18,6 +24,10 @@
18
24
 
19
25
  <custom-element name="ui-card-header">
20
26
  <style>
27
+ :host,
28
+ * {
29
+ touch-action: manipulation;
30
+ }
21
31
  :host {
22
32
  display: grid;
23
33
  grid-auto-rows: min-content;
@@ -39,6 +49,10 @@
39
49
  </script>
40
50
 
41
51
  <style>
52
+ :host,
53
+ * {
54
+ touch-action: manipulation;
55
+ }
42
56
  :host {
43
57
  display: block;
44
58
  }
@@ -55,6 +69,10 @@
55
69
 
56
70
  <custom-element name="ui-card-description">
57
71
  <style>
72
+ :host,
73
+ * {
74
+ touch-action: manipulation;
75
+ }
58
76
  :host {
59
77
  display: block;
60
78
  font-size: var(--text-sm);
@@ -67,6 +85,10 @@
67
85
 
68
86
  <custom-element name="ui-card-action">
69
87
  <style>
88
+ :host,
89
+ * {
90
+ touch-action: manipulation;
91
+ }
70
92
  :host {
71
93
  display: block;
72
94
  grid-column: 2;
@@ -80,6 +102,10 @@
80
102
 
81
103
  <custom-element name="ui-card-content">
82
104
  <style>
105
+ :host,
106
+ * {
107
+ touch-action: manipulation;
108
+ }
83
109
  :host {
84
110
  display: block;
85
111
  padding: 0 calc(var(--spacing) * 6);
@@ -91,6 +117,10 @@
91
117
 
92
118
  <custom-element name="ui-card-footer">
93
119
  <style>
120
+ :host,
121
+ * {
122
+ touch-action: manipulation;
123
+ }
94
124
  :host {
95
125
  display: flex;
96
126
  align-items: center;
@@ -65,6 +65,12 @@
65
65
  </script>
66
66
 
67
67
  <style>
68
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
69
+ touch-action does not reach shadow content in iOS Safari. */
70
+ :host,
71
+ * {
72
+ touch-action: manipulation;
73
+ }
68
74
  :host {
69
75
  --carousel-direction: row;
70
76
  --carousel-axis: x;
@@ -120,6 +126,10 @@
120
126
 
121
127
  <custom-element name="ui-carousel-content">
122
128
  <style>
129
+ :host,
130
+ * {
131
+ touch-action: manipulation;
132
+ }
123
133
  :host {
124
134
  display: flex;
125
135
  min-width: 0;
@@ -152,6 +162,10 @@
152
162
  </script>
153
163
 
154
164
  <style>
165
+ :host,
166
+ * {
167
+ touch-action: manipulation;
168
+ }
155
169
  :host {
156
170
  display: block;
157
171
  box-sizing: border-box;
@@ -167,6 +181,10 @@
167
181
 
168
182
  <custom-element name="ui-carousel-previous">
169
183
  <style>
184
+ :host,
185
+ * {
186
+ touch-action: manipulation;
187
+ }
170
188
  :host {
171
189
  display: inline-flex;
172
190
  }
@@ -177,6 +195,10 @@
177
195
 
178
196
  <custom-element name="ui-carousel-next">
179
197
  <style>
198
+ :host,
199
+ * {
200
+ touch-action: manipulation;
201
+ }
180
202
  :host {
181
203
  display: inline-flex;
182
204
  }
@@ -84,6 +84,12 @@
84
84
  </script>
85
85
 
86
86
  <style>
87
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
88
+ touch-action does not reach shadow content in iOS Safari. */
89
+ :host,
90
+ * {
91
+ touch-action: manipulation;
92
+ }
87
93
  :host {
88
94
  display: block;
89
95
  width: 100%;
@@ -15,6 +15,12 @@
15
15
  </script>
16
16
 
17
17
  <style>
18
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
19
+ touch-action does not reach shadow content in iOS Safari. */
20
+ :host,
21
+ * {
22
+ touch-action: manipulation;
23
+ }
18
24
  :host(:state(attr-checked)) label {
19
25
  color: var(--foreground);
20
26
  }
@@ -14,6 +14,12 @@
14
14
  </script>
15
15
 
16
16
  <style>
17
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
18
+ touch-action does not reach shadow content in iOS Safari. */
19
+ :host,
20
+ * {
21
+ touch-action: manipulation;
22
+ }
17
23
  :host {
18
24
  position: relative;
19
25
  display: block;
@@ -14,6 +14,12 @@
14
14
  </script>
15
15
 
16
16
  <style>
17
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
18
+ touch-action does not reach shadow content in iOS Safari. */
19
+ :host,
20
+ * {
21
+ touch-action: manipulation;
22
+ }
17
23
  :host {
18
24
  display: block;
19
25
  }
@@ -43,6 +49,10 @@
43
49
 
44
50
  <custom-element name="ui-collapsible-trigger" slot="trigger">
45
51
  <style>
52
+ :host,
53
+ * {
54
+ touch-action: manipulation;
55
+ }
46
56
  :host {
47
57
  display: flex;
48
58
  align-items: center;
@@ -63,6 +73,10 @@
63
73
 
64
74
  <custom-element name="ui-collapsible-content">
65
75
  <style>
76
+ :host,
77
+ * {
78
+ touch-action: manipulation;
79
+ }
66
80
  :host {
67
81
  display: flex;
68
82
  flex-direction: column;
@@ -55,6 +55,12 @@
55
55
  combo.query = "";
56
56
  combo.active = Math.max(0, combo.visible.findIndex((item) => item.chosen));
57
57
  refs.list.showPopover();
58
+ // Once open, the list keeps the side it opened on. Re-deciding while the page scrolls makes
59
+ // it jump, because iOS Safari's toolbar changes the viewport height mid-scroll.
60
+ // Firefox holds a box that does not fit against the screen edge, so touching it counts too.
61
+ internals.states.delete("flipped");
62
+ const box = refs.list.getBoundingClientRect();
63
+ if (box.bottom >= innerHeight || box.top <= 0) internals.states.add("flipped");
58
64
  }
59
65
 
60
66
  function close() {
@@ -89,6 +95,12 @@
89
95
  </script>
90
96
 
91
97
  <style>
98
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
99
+ touch-action does not reach shadow content in iOS Safari. */
100
+ :host,
101
+ * {
102
+ touch-action: manipulation;
103
+ }
92
104
  :host {
93
105
  display: block;
94
106
  min-width: calc(var(--spacing) * 48);
@@ -122,6 +134,12 @@
122
134
  font-size: var(--text-sm);
123
135
  outline: none;
124
136
  }
137
+ /* iOS Safari zooms in on a focused field whose text is under 16px. */
138
+ @media (pointer: coarse) {
139
+ input {
140
+ font-size: var(--text-base);
141
+ }
142
+ }
125
143
  .chevron {
126
144
  width: calc(var(--spacing) * 4);
127
145
  height: calc(var(--spacing) * 4);
@@ -143,9 +161,17 @@
143
161
  box-shadow: var(--shadow-md);
144
162
  inset: auto;
145
163
  position-anchor: --combobox;
164
+ height: fit-content;
146
165
  position-area: bottom span-right;
147
166
  position-try-fallbacks: flip-block;
148
167
  }
168
+ [role="listbox"]:popover-open {
169
+ position-try-fallbacks: none;
170
+ }
171
+ :host(:state(flipped)) [role="listbox"] {
172
+ position-area: top span-right;
173
+ margin: 0 0 calc(var(--spacing) * 1);
174
+ }
149
175
  [role="option"] {
150
176
  display: flex;
151
177
  align-items: center;
@@ -72,6 +72,12 @@
72
72
  </script>
73
73
 
74
74
  <style>
75
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
76
+ touch-action does not reach shadow content in iOS Safari. */
77
+ :host,
78
+ * {
79
+ touch-action: manipulation;
80
+ }
75
81
  :host {
76
82
  display: flex;
77
83
  flex-direction: column;
@@ -103,6 +109,12 @@
103
109
  font-size: var(--text-sm);
104
110
  outline: none;
105
111
  }
112
+ /* iOS Safari zooms in on a focused field whose text is under 16px. */
113
+ @media (pointer: coarse) {
114
+ input {
115
+ font-size: var(--text-base);
116
+ }
117
+ }
106
118
  input::placeholder {
107
119
  color: var(--muted-foreground);
108
120
  }
@@ -10,6 +10,12 @@
10
10
  </script>
11
11
 
12
12
  <style>
13
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
14
+ touch-action does not reach shadow content in iOS Safari. */
15
+ :host,
16
+ * {
17
+ touch-action: manipulation;
18
+ }
13
19
  :host {
14
20
  display: contents;
15
21
  }
@@ -20,6 +26,10 @@
20
26
 
21
27
  <custom-element name="ui-context-menu-trigger">
22
28
  <style>
29
+ :host,
30
+ * {
31
+ touch-action: manipulation;
32
+ }
23
33
  :host {
24
34
  display: block;
25
35
  }
@@ -48,6 +58,10 @@
48
58
  </script>
49
59
 
50
60
  <style>
61
+ :host,
62
+ * {
63
+ touch-action: manipulation;
64
+ }
51
65
  :host {
52
66
  position: fixed;
53
67
  box-sizing: border-box;
@@ -74,12 +74,21 @@
74
74
  observe(() => {
75
75
  const dialog = refs.dialog;
76
76
  if (!dialog || props.open === dialog.open) return;
77
- if (props.open) dialog.showModal();
78
- else dialog.close();
77
+ if (!props.open) return dialog.close();
78
+ dialog.showModal();
79
+ // Left to the browser, focus lands on the first button (often close) with a focus ring after a
80
+ // tap, and Firefox and WebKit ignore autofocus on slotted content.
81
+ (host.querySelector("[autofocus]") ?? dialog).focus();
79
82
  });
80
83
  </script>
81
84
 
82
85
  <style>
86
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
87
+ touch-action does not reach shadow content in iOS Safari. */
88
+ :host,
89
+ * {
90
+ touch-action: manipulation;
91
+ }
83
92
  :host {
84
93
  display: contents;
85
94
  }
@@ -87,7 +96,10 @@
87
96
  box-sizing: border-box;
88
97
  width: 100%;
89
98
  max-width: min(calc(var(--spacing) * 128), calc(100% - calc(var(--spacing) * 8)));
90
- max-height: calc(100% - calc(var(--spacing) * 8));
99
+ /* Without an explicit height, a dialog pinned to both edges of the screen can stretch to
100
+ full height. svh stays the same while a phone's address bar shows and hides. */
101
+ height: fit-content;
102
+ max-height: calc(100svh - calc(var(--spacing) * 8));
91
103
  padding: calc(var(--spacing) * 6);
92
104
  border: 1px solid var(--border);
93
105
  border-radius: var(--radius-lg);
@@ -96,8 +108,13 @@
96
108
  box-shadow: var(--shadow-lg);
97
109
  overflow: auto;
98
110
  }
111
+ dialog:focus {
112
+ outline: none;
113
+ }
99
114
  dialog[open] {
100
115
  display: grid;
116
+ /* Safari stretches a grid overlay to its max-height unless the rows are packed at the start. */
117
+ align-content: start;
101
118
  gap: calc(var(--spacing) * 4);
102
119
  animation: zoom 0.2s ease-out;
103
120
  }
@@ -116,7 +133,11 @@
116
133
  display: flex;
117
134
  flex-direction: column;
118
135
  }
136
+ [part="body"] {
137
+ display: contents;
138
+ }
119
139
  :host(:is(:state(attr-side-is-left), :state(attr-side-is-right))) [part="body"] {
140
+ display: block;
120
141
  flex: 1;
121
142
  }
122
143
  :host(:state(attr-side-is-right)) dialog {
@@ -185,6 +206,7 @@
185
206
  <dialog
186
207
  ref="dialog"
187
208
  part="dialog"
209
+ tabindex="-1"
188
210
  _aria-label="#dialog.label"
189
211
  _aria-description="#dialog.description"
190
212
  onclose="closed()"
@@ -201,6 +223,10 @@
201
223
 
202
224
  <custom-element name="ui-dialog-trigger" slot="trigger">
203
225
  <style>
226
+ :host,
227
+ * {
228
+ touch-action: manipulation;
229
+ }
204
230
  :host,
205
231
  span {
206
232
  display: contents;
@@ -216,6 +242,10 @@
216
242
  </script>
217
243
 
218
244
  <style>
245
+ :host,
246
+ * {
247
+ touch-action: manipulation;
248
+ }
219
249
  :host,
220
250
  span {
221
251
  display: contents;
@@ -227,6 +257,10 @@
227
257
 
228
258
  <custom-element name="ui-dialog-header" slot="header">
229
259
  <style>
260
+ :host,
261
+ * {
262
+ touch-action: manipulation;
263
+ }
230
264
  :host {
231
265
  display: grid;
232
266
  gap: calc(var(--spacing) * 2);
@@ -238,6 +272,10 @@
238
272
 
239
273
  <custom-element name="ui-dialog-title">
240
274
  <style>
275
+ :host,
276
+ * {
277
+ touch-action: manipulation;
278
+ }
241
279
  :host {
242
280
  display: block;
243
281
  }
@@ -254,6 +292,10 @@
254
292
 
255
293
  <custom-element name="ui-dialog-description">
256
294
  <style>
295
+ :host,
296
+ * {
297
+ touch-action: manipulation;
298
+ }
257
299
  :host {
258
300
  display: block;
259
301
  }
@@ -269,6 +311,10 @@
269
311
 
270
312
  <custom-element name="ui-dialog-footer" slot="footer">
271
313
  <style>
314
+ :host,
315
+ * {
316
+ touch-action: manipulation;
317
+ }
272
318
  :host {
273
319
  display: flex;
274
320
  flex-wrap: wrap;
@@ -8,6 +8,12 @@
8
8
  </script>
9
9
 
10
10
  <style>
11
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
12
+ touch-action does not reach shadow content in iOS Safari. */
13
+ :host,
14
+ * {
15
+ touch-action: manipulation;
16
+ }
11
17
  :host {
12
18
  display: contents;
13
19
  }
@@ -64,20 +64,33 @@
64
64
  observe(() => {
65
65
  const dialog = refs.dialog;
66
66
  if (!dialog || props.open === dialog.open) return;
67
- if (props.open) dialog.showModal();
68
- else dialog.close();
67
+ if (!props.open) return dialog.close();
68
+ dialog.showModal();
69
+ // Left to the browser, focus lands on the first button (often close) with a focus ring after a
70
+ // tap, and Firefox and WebKit ignore autofocus on slotted content.
71
+ (host.querySelector("[autofocus]") ?? dialog).focus();
69
72
  });
70
73
  </script>
71
74
 
72
75
  <style>
76
+ /* A double tap must not zoom the page. Set inside the shadow root, because a page-level
77
+ touch-action does not reach shadow content in iOS Safari. */
78
+ :host,
79
+ * {
80
+ touch-action: manipulation;
81
+ }
73
82
  :host {
74
83
  display: contents;
75
84
  }
85
+ dialog:focus {
86
+ outline: none;
87
+ }
76
88
  dialog {
77
89
  box-sizing: border-box;
78
90
  width: 100%;
79
91
  max-width: none;
80
- max-height: 80dvh;
92
+ height: fit-content;
93
+ max-height: 80svh;
81
94
  margin: auto 0 0;
82
95
  padding: calc(var(--spacing) * 4) calc(var(--spacing) * 6) calc(var(--spacing) * 6);
83
96
  border: 1px solid var(--border);
@@ -103,9 +116,14 @@
103
116
  }
104
117
  dialog[open] {
105
118
  display: grid;
119
+ /* Safari stretches a grid overlay to its max-height unless the rows are packed at the start. */
120
+ align-content: start;
106
121
  gap: calc(var(--spacing) * 4);
107
122
  animation: slide 0.25s ease-out;
108
123
  }
124
+ [part="body"] {
125
+ display: contents;
126
+ }
109
127
  dialog::backdrop {
110
128
  background: rgb(0 0 0 / 0.5);
111
129
  }
@@ -138,6 +156,7 @@
138
156
  <dialog
139
157
  ref="dialog"
140
158
  part="dialog"
159
+ tabindex="-1"
141
160
  _aria-label="#drawer.label"
142
161
  _aria-description="#drawer.description"
143
162
  onclose="closed()"
@@ -151,6 +170,10 @@
151
170
 
152
171
  <custom-element name="ui-drawer-trigger" slot="trigger">
153
172
  <style>
173
+ :host,
174
+ * {
175
+ touch-action: manipulation;
176
+ }
154
177
  :host,
155
178
  span {
156
179
  display: contents;
@@ -162,6 +185,10 @@
162
185
 
163
186
  <custom-element name="ui-drawer-close">
164
187
  <style>
188
+ :host,
189
+ * {
190
+ touch-action: manipulation;
191
+ }
165
192
  :host,
166
193
  span {
167
194
  display: contents;
@@ -173,6 +200,10 @@
173
200
 
174
201
  <custom-element name="ui-drawer-header" slot="header">
175
202
  <style>
203
+ :host,
204
+ * {
205
+ touch-action: manipulation;
206
+ }
176
207
  :host {
177
208
  display: grid;
178
209
  gap: calc(var(--spacing) * 1.5);
@@ -184,6 +215,10 @@
184
215
 
185
216
  <custom-element name="ui-drawer-title">
186
217
  <style>
218
+ :host,
219
+ * {
220
+ touch-action: manipulation;
221
+ }
187
222
  :host {
188
223
  display: block;
189
224
  }
@@ -199,6 +234,10 @@
199
234
 
200
235
  <custom-element name="ui-drawer-description">
201
236
  <style>
237
+ :host,
238
+ * {
239
+ touch-action: manipulation;
240
+ }
202
241
  :host {
203
242
  display: block;
204
243
  }
@@ -214,6 +253,10 @@
214
253
 
215
254
  <custom-element name="ui-drawer-footer" slot="footer">
216
255
  <style>
256
+ :host,
257
+ * {
258
+ touch-action: manipulation;
259
+ }
217
260
  :host {
218
261
  display: grid;
219
262
  gap: calc(var(--spacing) * 2);