@raylang/elements 0.6.0 → 0.8.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.
- package/base.css +1 -1
- package/check/check.d.ts +2 -0
- package/check/check.js +18 -4
- package/check/cli.js +10 -6
- package/components/accordion/accordion.js +8 -5
- package/components/alert/alert.js +3 -2
- package/components/attachment/attachment.js +5 -6
- package/components/avatar/avatar.js +1 -1
- package/components/blockquote/blockquote.js +2 -2
- package/components/bottom-nav/bottom-nav.js +5 -3
- package/components/breadcrumbs/breadcrumbs.js +1 -1
- package/components/carousel/carousel.js +26 -14
- package/components/checkbox/checkbox.js +6 -3
- package/components/checkbox-group/checkbox-group.js +1 -1
- package/components/code/code.js +9 -7
- package/components/color-picker/color-picker.js +6 -6
- package/components/combobox/combobox.js +5 -4
- package/components/command-palette/command-palette.js +2 -2
- package/components/date-picker/date-picker.js +9 -7
- package/components/dialog/dialog.js +2 -2
- package/components/drawer/drawer.js +28 -10
- package/components/file-upload/file-upload.js +1 -1
- package/components/icon/icon.js +9 -1
- package/components/indicator/indicator.js +2 -1
- package/components/menu/menu.js +1 -1
- package/components/nav-link/nav-link.js +6 -3
- package/components/notification/notification.js +16 -13
- package/components/number-input/number-input.js +1 -1
- package/components/pagination/pagination.js +4 -4
- package/components/password-input/password-input.js +2 -2
- package/components/progress/progress.js +2 -2
- package/components/radio/radio.js +4 -2
- package/components/scroll-area/scroll-area.js +14 -11
- package/components/search/search.js +5 -5
- package/components/segmented-control/segmented-control.js +5 -3
- package/components/select/select.js +5 -5
- package/components/separator/separator.js +8 -3
- package/components/sidebar/sidebar.js +14 -6
- package/components/slider/slider.js +5 -2
- package/components/split/split.js +12 -6
- package/components/stat/stat.js +1 -1
- package/components/step-dots/step-dots.js +11 -2
- package/components/stepper/stepper.js +5 -5
- package/components/switch/switch.js +3 -3
- package/components/table/table.js +6 -6
- package/components/tabs/tabs.js +11 -8
- package/components/theme-toggle/theme-toggle.js +2 -2
- package/components/timeline/timeline.js +13 -8
- package/components/toggle-group/toggle-group.js +6 -4
- package/core/core.js +10 -4
- package/core/direction.js +19 -0
- package/core/icons.js +107 -8
- package/core/position.js +11 -3
- package/core/styles.js +1 -1
- package/core.js +1 -0
- package/custom-elements.json +151 -79
- package/llms-full.txt +75 -31
- package/llms.txt +32 -3
- package/package.json +1 -1
- package/ray-elements.d.ts +56 -39
- package/vscode.html-custom-data.json +1102 -39
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// arithmetic), so no time zone shifts a day.
|
|
13
13
|
|
|
14
14
|
import { RayElement, css, define } from "../../core/core.js";
|
|
15
|
+
import { inlineKey } from "../../core/direction.js";
|
|
15
16
|
import { sizeStyles, radiusStyles } from "../../core/styles.js";
|
|
16
17
|
import { place, autoUpdate } from "../../core/position.js";
|
|
17
18
|
import { dismissable } from "../../core/dismiss.js";
|
|
@@ -144,9 +145,9 @@ export class RayCalendar extends RayElement {
|
|
|
144
145
|
static styles = [sizeStyles, calendarStyles];
|
|
145
146
|
static template = `
|
|
146
147
|
<div class="header" part="header">
|
|
147
|
-
<button class="nav prev focus-ring" part="nav" type="button" data-ray-icon="
|
|
148
|
+
<button class="nav prev focus-ring" part="nav" type="button" data-ray-icon="ui.previous"></button>
|
|
148
149
|
<div class="caption" part="caption" id="caption" aria-live="polite"></div>
|
|
149
|
-
<button class="nav next focus-ring" part="nav" type="button" data-ray-icon="
|
|
150
|
+
<button class="nav next focus-ring" part="nav" type="button" data-ray-icon="ui.next"></button>
|
|
150
151
|
</div>
|
|
151
152
|
<table role="grid" aria-labelledby="caption" part="grid"><thead><tr></tr></thead><tbody></tbody></table>`;
|
|
152
153
|
|
|
@@ -268,7 +269,8 @@ export class RayCalendar extends RayElement {
|
|
|
268
269
|
const d = this._focusDate();
|
|
269
270
|
const first = this.firstDay ?? firstDayOf(localeOf(this));
|
|
270
271
|
let next = null;
|
|
271
|
-
|
|
272
|
+
// The grid follows the writing direction: right to left, ArrowLeft is the next day.
|
|
273
|
+
switch (inlineKey(e.key, this)) {
|
|
272
274
|
case "ArrowLeft": next = addDays(d, -1); break;
|
|
273
275
|
case "ArrowRight": next = addDays(d, 1); break;
|
|
274
276
|
case "ArrowUp": next = addDays(d, -7); break;
|
|
@@ -321,7 +323,7 @@ const pickerStyles = css`
|
|
|
321
323
|
gap: var(--ray-space-2);
|
|
322
324
|
width: 100%;
|
|
323
325
|
min-width: 0;
|
|
324
|
-
text-align:
|
|
326
|
+
text-align: start;
|
|
325
327
|
color: inherit;
|
|
326
328
|
background: transparent;
|
|
327
329
|
border: 0;
|
|
@@ -340,7 +342,7 @@ const pickerStyles = css`
|
|
|
340
342
|
justify-content: center;
|
|
341
343
|
width: calc(var(--_h) * 0.6);
|
|
342
344
|
height: calc(var(--_h) * 0.6);
|
|
343
|
-
margin-
|
|
345
|
+
margin-inline-end: calc(var(--_px) * 0.4);
|
|
344
346
|
padding: 0;
|
|
345
347
|
color: var(--ray-muted-foreground);
|
|
346
348
|
background: transparent;
|
|
@@ -382,8 +384,8 @@ export class RayDatePicker extends FieldControl {
|
|
|
382
384
|
static template = fieldTemplate(
|
|
383
385
|
`<input id="control" class="native" part="input" type="date" tabindex="-1" aria-hidden="true">` +
|
|
384
386
|
`<button type="button" id="trigger" class="control trigger" part="trigger" aria-haspopup="dialog" aria-expanded="false" aria-controls="panel">` +
|
|
385
|
-
`<span class="icon" aria-hidden="true" data-ray-icon="calendar"></span><span class="value" id="value"></span></button>` +
|
|
386
|
-
`<button type="button" class="clear focus-ring" part="clear" aria-label="Clear date" hidden><span data-ray-icon="
|
|
387
|
+
`<span class="icon" aria-hidden="true" data-ray-icon="ui.calendar"></span><span class="value" id="value"></span></button>` +
|
|
388
|
+
`<button type="button" class="clear focus-ring" part="clear" aria-label="Clear date" hidden><span data-ray-icon="ui.clear" aria-hidden="true"></span></button>`,
|
|
387
389
|
{ sections: false },
|
|
388
390
|
) + `<div class="panel" id="panel" part="dropdown" popover="manual" role="dialog" aria-label="Choose a date"><ray-calendar part="calendar" exportparts="day, caption, nav, grid"></ray-calendar></div>`;
|
|
389
391
|
|
|
@@ -66,7 +66,7 @@ const styles = css`
|
|
|
66
66
|
justify-content: center;
|
|
67
67
|
width: var(--ray-control-height-xs);
|
|
68
68
|
height: var(--ray-control-height-xs);
|
|
69
|
-
margin-
|
|
69
|
+
margin-inline-start: auto;
|
|
70
70
|
padding: 0;
|
|
71
71
|
color: var(--ray-muted-foreground);
|
|
72
72
|
background: transparent;
|
|
@@ -106,7 +106,7 @@ export class RayDialog extends RayElement {
|
|
|
106
106
|
<dialog part="dialog" aria-labelledby="title">
|
|
107
107
|
<header class="header" part="header">
|
|
108
108
|
<div class="heading"><h2 class="title" id="title" part="title"></h2><p class="description" id="description" part="description" hidden></p></div>
|
|
109
|
-
<div class="actions"><slot name="header"></slot><button class="close focus-ring" part="close" type="button" aria-label="Close"><span data-ray-icon="close" aria-hidden="true"></span></button></div>
|
|
109
|
+
<div class="actions"><slot name="header"></slot><button class="close focus-ring" part="close" type="button" aria-label="Close"><span data-ray-icon="ui.close" aria-hidden="true"></span></button></div>
|
|
110
110
|
</header>
|
|
111
111
|
<div class="body" part="body"><slot></slot></div>
|
|
112
112
|
<footer class="footer" part="footer"><slot name="footer"></slot></footer>
|
|
@@ -32,24 +32,41 @@ const styles = css`
|
|
|
32
32
|
dialog[open] { display: flex; flex-direction: column; }
|
|
33
33
|
dialog::backdrop { animation: ray-fade-in var(--ray-duration-base) var(--ray-easing-out); }
|
|
34
34
|
|
|
35
|
-
:
|
|
36
|
-
|
|
35
|
+
/* position: start/end follow the writing direction, left/right are sides
|
|
36
|
+
of the screen. */
|
|
37
|
+
dialog {
|
|
38
|
+
inset-block: var(--_inset);
|
|
39
|
+
inset-inline: auto var(--_inset);
|
|
37
40
|
width: min(var(--_size), calc(100vw - 2 * var(--_inset)));
|
|
38
41
|
height: calc(100dvh - 2 * var(--_inset));
|
|
39
|
-
border-
|
|
42
|
+
border-inline-start-width: 1px;
|
|
43
|
+
--_from: translateX(100%);
|
|
44
|
+
}
|
|
45
|
+
dialog:dir(rtl) { --_from: translateX(-100%); }
|
|
46
|
+
:host([position="start"]) dialog {
|
|
47
|
+
inset-inline: var(--_inset) auto;
|
|
48
|
+
border-inline-start-width: 0;
|
|
49
|
+
border-inline-end-width: 1px;
|
|
50
|
+
--_from: translateX(-100%);
|
|
51
|
+
}
|
|
52
|
+
:host([position="start"]) dialog:dir(rtl) { --_from: translateX(100%); }
|
|
53
|
+
:host([position="right"]) dialog {
|
|
54
|
+
inset: var(--_inset) var(--_inset) var(--_inset) auto; /* physical */
|
|
55
|
+
border-width: 0;
|
|
56
|
+
border-left-width: 1px; /* physical */
|
|
40
57
|
--_from: translateX(100%);
|
|
41
58
|
}
|
|
42
59
|
:host([position="left"]) dialog {
|
|
43
|
-
inset: var(--_inset) auto var(--_inset) var(--_inset);
|
|
44
|
-
width:
|
|
45
|
-
|
|
46
|
-
border-right-width: 1px;
|
|
60
|
+
inset: var(--_inset) auto var(--_inset) var(--_inset); /* physical */
|
|
61
|
+
border-width: 0;
|
|
62
|
+
border-right-width: 1px; /* physical */
|
|
47
63
|
--_from: translateX(-100%);
|
|
48
64
|
}
|
|
49
65
|
:host([position="top"]) dialog {
|
|
50
66
|
inset: var(--_inset) var(--_inset) auto var(--_inset);
|
|
51
67
|
width: calc(100vw - 2 * var(--_inset));
|
|
52
68
|
height: min(var(--_size), calc(100dvh - 2 * var(--_inset)));
|
|
69
|
+
border-width: 0;
|
|
53
70
|
border-bottom-width: 1px;
|
|
54
71
|
--_from: translateY(-100%);
|
|
55
72
|
}
|
|
@@ -57,6 +74,7 @@ const styles = css`
|
|
|
57
74
|
inset: auto var(--_inset) var(--_inset) var(--_inset);
|
|
58
75
|
width: calc(100vw - 2 * var(--_inset));
|
|
59
76
|
height: min(var(--_size), calc(100dvh - 2 * var(--_inset)));
|
|
77
|
+
border-width: 0;
|
|
60
78
|
border-top-width: 1px;
|
|
61
79
|
--_from: translateY(100%);
|
|
62
80
|
}
|
|
@@ -83,7 +101,7 @@ const styles = css`
|
|
|
83
101
|
justify-content: center;
|
|
84
102
|
width: var(--ray-control-height-xs);
|
|
85
103
|
height: var(--ray-control-height-xs);
|
|
86
|
-
margin-
|
|
104
|
+
margin-inline-start: auto;
|
|
87
105
|
padding: 0;
|
|
88
106
|
color: var(--ray-muted-foreground);
|
|
89
107
|
background: transparent;
|
|
@@ -101,7 +119,7 @@ const styles = css`
|
|
|
101
119
|
export class RayDrawer extends RayDialog {
|
|
102
120
|
static properties = {
|
|
103
121
|
...RayDialog.properties,
|
|
104
|
-
position: { type: String, reflect: true, default: "
|
|
122
|
+
position: { type: String, reflect: true, default: "end" },
|
|
105
123
|
size: { type: String, reflect: true, default: "md" },
|
|
106
124
|
modeless: { type: Boolean, reflect: true },
|
|
107
125
|
floating: { type: Boolean, reflect: true },
|
|
@@ -111,7 +129,7 @@ export class RayDrawer extends RayDialog {
|
|
|
111
129
|
<dialog part="drawer" aria-labelledby="title">
|
|
112
130
|
<header class="header" part="header">
|
|
113
131
|
<div class="heading"><h2 class="title" id="title" part="title"></h2><p class="description" id="description" part="description" hidden></p></div>
|
|
114
|
-
<div class="actions"><slot name="header"></slot><button class="close focus-ring" part="close" type="button" aria-label="Close"><span data-ray-icon="close" aria-hidden="true"></span></button></div>
|
|
132
|
+
<div class="actions"><slot name="header"></slot><button class="close focus-ring" part="close" type="button" aria-label="Close"><span data-ray-icon="ui.close" aria-hidden="true"></span></button></div>
|
|
115
133
|
</header>
|
|
116
134
|
<div class="body" part="body"><slot></slot></div>
|
|
117
135
|
<footer class="footer" part="footer"><slot name="footer"></slot></footer>
|
|
@@ -233,7 +233,7 @@ export class RayFileUpload extends FormControl {
|
|
|
233
233
|
<span class="label" part="label" id="label" hidden><span class="label-text"></span><span class="required" aria-hidden="true" hidden> *</span></span>
|
|
234
234
|
<div class="description" part="description" id="description" hidden></div>
|
|
235
235
|
<button type="button" class="zone" part="dropzone" aria-labelledby="label title hint pill" aria-describedby="description error">
|
|
236
|
-
<span class="glyph" part="icon"><slot name="icon"><span data-ray-icon="upload"></span></slot></span>
|
|
236
|
+
<span class="glyph" part="icon"><slot name="icon"><span data-ray-icon="ui.upload"></span></slot></span>
|
|
237
237
|
<span class="copy">
|
|
238
238
|
<span class="title" id="title"></span>
|
|
239
239
|
<span class="hint" id="hint" part="hint"></span>
|
package/components/icon/icon.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// <ray-icon name="…">: an icon of the registry (setIcons), so the app's own
|
|
2
2
|
// markup uses the same set as the components. Decorative unless `label`.
|
|
3
|
+
// `filled` draws the icon's filled version (a state: active, selected,
|
|
4
|
+
// favorite), or the outline one when it has none. `mirror` mirrors it right
|
|
5
|
+
// to left (a back arrow), as the roles that point along the text (ui.next…) are.
|
|
3
6
|
|
|
4
7
|
import { RayElement, css, define } from "../../core/core.js";
|
|
5
8
|
|
|
@@ -11,6 +14,7 @@ const styles = css`
|
|
|
11
14
|
size) must not reach the icon. size= overrides it inline. */
|
|
12
15
|
:host { --_size: 1em; display: inline-flex; vertical-align: -0.125em; line-height: 1; }
|
|
13
16
|
.icon svg { width: var(--_size); height: var(--_size); }
|
|
17
|
+
:host([mirror]) .icon:dir(rtl) > svg { transform: scaleX(-1); }
|
|
14
18
|
`;
|
|
15
19
|
|
|
16
20
|
export class RayIcon extends RayElement {
|
|
@@ -20,6 +24,8 @@ export class RayIcon extends RayElement {
|
|
|
20
24
|
label: { type: String },
|
|
21
25
|
color: { type: String, reflect: true },
|
|
22
26
|
stroke: { type: Number },
|
|
27
|
+
filled: { type: Boolean, reflect: true },
|
|
28
|
+
mirror: { type: Boolean, reflect: true },
|
|
23
29
|
};
|
|
24
30
|
static styles = [styles];
|
|
25
31
|
static template = `<span class="icon" part="icon" data-ray-icon=""></span>`;
|
|
@@ -31,8 +37,10 @@ export class RayIcon extends RayElement {
|
|
|
31
37
|
|
|
32
38
|
update() {
|
|
33
39
|
const icon = this.$(".icon");
|
|
34
|
-
|
|
40
|
+
const filled = Boolean(this.filled);
|
|
41
|
+
if (icon.getAttribute("data-ray-icon") !== (this.name ?? "") || icon.hasAttribute("data-ray-icon-filled") !== filled) {
|
|
35
42
|
icon.setAttribute("data-ray-icon", this.name ?? "");
|
|
43
|
+
icon.toggleAttribute("data-ray-icon-filled", filled);
|
|
36
44
|
this.paintIcons();
|
|
37
45
|
}
|
|
38
46
|
// xs…xl from the scale, or any CSS length.
|
|
@@ -21,7 +21,7 @@ const styles = css`
|
|
|
21
21
|
position: absolute;
|
|
22
22
|
z-index: 1;
|
|
23
23
|
top: var(--_top);
|
|
24
|
-
|
|
24
|
+
inset-inline-start: var(--_left);
|
|
25
25
|
display: inline-flex;
|
|
26
26
|
align-items: center;
|
|
27
27
|
justify-content: center;
|
|
@@ -34,6 +34,7 @@ const styles = css`
|
|
|
34
34
|
transform: translate(-50%, -50%);
|
|
35
35
|
pointer-events: none;
|
|
36
36
|
}
|
|
37
|
+
.indicator:dir(rtl) { transform: translate(50%, -50%); }
|
|
37
38
|
.indicator[data-label] {
|
|
38
39
|
height: auto;
|
|
39
40
|
min-height: var(--_size);
|
package/components/menu/menu.js
CHANGED
|
@@ -230,7 +230,7 @@ const itemStyles = css`
|
|
|
230
230
|
.section { display: inline-flex; align-items: center; color: var(--ray-muted-foreground); }
|
|
231
231
|
:host([color]) .section { color: inherit; }
|
|
232
232
|
.section:not([data-filled]) { display: none; }
|
|
233
|
-
.right { margin-
|
|
233
|
+
.right { margin-inline-start: var(--ray-space-6); font-size: var(--ray-font-size-xs); }
|
|
234
234
|
::slotted(svg) { width: 1rem; height: 1rem; }
|
|
235
235
|
`;
|
|
236
236
|
|
|
@@ -17,7 +17,7 @@ const styles = css`
|
|
|
17
17
|
font: inherit;
|
|
18
18
|
font-size: var(--ray-font-size-sm);
|
|
19
19
|
color: var(--ray-foreground);
|
|
20
|
-
text-align:
|
|
20
|
+
text-align: start;
|
|
21
21
|
text-decoration: none;
|
|
22
22
|
background: transparent;
|
|
23
23
|
border: 0;
|
|
@@ -43,6 +43,8 @@ const styles = css`
|
|
|
43
43
|
.chevron svg { width: 0.875rem; height: 0.875rem; }
|
|
44
44
|
:host([data-parent]) .chevron { display: inline-flex; }
|
|
45
45
|
:host([opened]) .chevron { transform: rotate(90deg); }
|
|
46
|
+
/* Right to left the chevron is mirrored (it points left): it turns the other way. */
|
|
47
|
+
:host([opened]) .chevron:dir(rtl) { transform: rotate(-90deg); }
|
|
46
48
|
/* Children expand and collapse: a 0fr → 1fr grid row animates the height
|
|
47
49
|
in both directions without measuring it (like the accordion). */
|
|
48
50
|
.children {
|
|
@@ -58,14 +60,14 @@ const styles = css`
|
|
|
58
60
|
:host([icon-only]) .body, :host([icon-only]) .chevron, :host([icon-only]) .section.right { display: none; }
|
|
59
61
|
:host([icon-only]) .children { display: none; }
|
|
60
62
|
/* 2px of room so the focus ring of a child is not clipped. */
|
|
61
|
-
.children-inner { min-height: 0; overflow: hidden; margin: -2px 0; padding: 2px
|
|
63
|
+
.children-inner { min-height: 0; overflow: hidden; margin: -2px 0; padding-block: 2px; padding-inline-start: var(--ray-space-5); }
|
|
62
64
|
`;
|
|
63
65
|
|
|
64
66
|
const INNER = `
|
|
65
67
|
<span class="section" part="left-section"><slot name="left"></slot></span>
|
|
66
68
|
<span class="body"><span class="label" part="label"></span><span class="description" part="description"></span></span>
|
|
67
69
|
<span class="section right" part="right-section"><slot name="right"></slot></span>
|
|
68
|
-
<span class="chevron" aria-hidden="true" data-ray-icon="
|
|
70
|
+
<span class="chevron" aria-hidden="true" data-ray-icon="ui.disclosure"></span>`;
|
|
69
71
|
|
|
70
72
|
export class RayNavLink extends RayElement {
|
|
71
73
|
static delegatesFocus = true;
|
|
@@ -153,6 +155,7 @@ export class RayNavLink extends RayElement {
|
|
|
153
155
|
});
|
|
154
156
|
this.shadowRoot.prepend(el);
|
|
155
157
|
this._el = el;
|
|
158
|
+
this.paintIcons(el);
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
161
|
|
|
@@ -17,7 +17,8 @@ const notificationStyles = css`
|
|
|
17
17
|
display: flex;
|
|
18
18
|
align-items: flex-start;
|
|
19
19
|
gap: var(--ray-space-3);
|
|
20
|
-
padding: var(--ray-space-3)
|
|
20
|
+
padding: var(--ray-space-3);
|
|
21
|
+
padding-inline-start: var(--ray-space-4);
|
|
21
22
|
overflow: hidden;
|
|
22
23
|
color: var(--ray-popover-foreground);
|
|
23
24
|
background: var(--ray-popover);
|
|
@@ -29,7 +30,7 @@ const notificationStyles = css`
|
|
|
29
30
|
/* Behind the front toast of a collapsed stack: only its edge shows. */
|
|
30
31
|
.card > * { transition: opacity var(--ray-duration-base) var(--ray-easing-standard); }
|
|
31
32
|
:host([data-behind]) .card > * { opacity: 0; }
|
|
32
|
-
.card[data-icon], .card[data-loading], .card[data-glyph] { padding-
|
|
33
|
+
.card[data-icon], .card[data-loading], .card[data-glyph] { padding-inline-start: var(--ray-space-3); }
|
|
33
34
|
.icon {
|
|
34
35
|
display: none;
|
|
35
36
|
flex: none;
|
|
@@ -150,7 +151,7 @@ export class RayNotification extends RayElement {
|
|
|
150
151
|
<div class="message" part="message"><slot></slot></div>
|
|
151
152
|
</div>
|
|
152
153
|
<button class="action focus-ring" part="action" type="button" hidden></button>
|
|
153
|
-
<button class="close focus-ring" part="close" type="button" aria-label="Close notification"><span data-ray-icon="close" aria-hidden="true"></span></button>
|
|
154
|
+
<button class="close focus-ring" part="close" type="button" aria-label="Close notification"><span data-ray-icon="ui.close" aria-hidden="true"></span></button>
|
|
154
155
|
</div>`;
|
|
155
156
|
|
|
156
157
|
constructor() {
|
|
@@ -201,14 +202,16 @@ const toasterStyles = css`
|
|
|
201
202
|
:host(:not(:popover-open)) { display: none; }
|
|
202
203
|
:host([position^="top"]) { top: 0; }
|
|
203
204
|
:host([position^="bottom"]), :host(:not([position])) { bottom: 0; }
|
|
204
|
-
|
|
205
|
-
:host([position$="
|
|
206
|
-
:host([position$="
|
|
205
|
+
/* -start/-end follow the writing direction; -left/-right are sides of the screen. */
|
|
206
|
+
:host([position$="-end"]), :host(:not([position])) { inset-inline-end: 0; }
|
|
207
|
+
:host([position$="-start"]) { inset-inline-start: 0; }
|
|
208
|
+
:host([position$="right"]) { right: 0; } /* physical */
|
|
209
|
+
:host([position$="left"]) { left: 0; } /* physical */
|
|
210
|
+
:host([position$="center"]) { left: 50%; transform: translateX(-50%); } /* physical */
|
|
207
211
|
/* Toasts are placed by layout(): absolute, stacked from the edge. */
|
|
208
212
|
::slotted(*) {
|
|
209
213
|
position: absolute;
|
|
210
|
-
|
|
211
|
-
right: var(--ray-space-4);
|
|
214
|
+
inset-inline: var(--ray-space-4);
|
|
212
215
|
pointer-events: auto;
|
|
213
216
|
touch-action: pan-y;
|
|
214
217
|
transform-origin: 50% 100%;
|
|
@@ -222,10 +225,10 @@ const toasterStyles = css`
|
|
|
222
225
|
::slotted([data-swiping]) { transition: none; }
|
|
223
226
|
/* Phones: full width at the bottom (or top), wherever the desktop position is. */
|
|
224
227
|
@media (max-width: 40em) {
|
|
225
|
-
:host { width: auto;
|
|
228
|
+
:host { width: auto; inset-inline: 0; transform: none; padding: var(--ray-space-3); }
|
|
226
229
|
:host([position^="top"]) { top: 0; bottom: auto; }
|
|
227
230
|
:host(:not([position^="top"])) { bottom: 0; top: auto; }
|
|
228
|
-
::slotted(*) {
|
|
231
|
+
::slotted(*) { inset-inline: var(--ray-space-3); }
|
|
229
232
|
:host(:not([position^="top"])) ::slotted(*) { bottom: var(--ray-space-3); }
|
|
230
233
|
:host([position^="top"]) ::slotted(*) { top: var(--ray-space-3); }
|
|
231
234
|
}
|
|
@@ -272,7 +275,7 @@ const SHRINK = 0.05;
|
|
|
272
275
|
|
|
273
276
|
export class RayToaster extends RayElement {
|
|
274
277
|
static properties = {
|
|
275
|
-
position: { type: String, reflect: true, default: "bottom-
|
|
278
|
+
position: { type: String, reflect: true, default: "bottom-end" },
|
|
276
279
|
limit: { type: Number, default: 5 },
|
|
277
280
|
// Toasts visible in the collapsed stack (the rest wait, invisible).
|
|
278
281
|
visible: { type: Number, default: 3 },
|
|
@@ -358,7 +361,7 @@ export class RayToaster extends RayElement {
|
|
|
358
361
|
/** Places every toast: a stack (collapsed) or a list (expanded). */
|
|
359
362
|
layout() {
|
|
360
363
|
const toasts = this.toasts;
|
|
361
|
-
const top = (this.position ?? "bottom-
|
|
364
|
+
const top = (this.position ?? "bottom-end").startsWith("top");
|
|
362
365
|
const dir = top ? 1 : -1;
|
|
363
366
|
const heights = toasts.map((el) => el.shadowRoot?.querySelector(".card")?.offsetHeight || el.offsetHeight || 0);
|
|
364
367
|
const front = heights[0] ?? 0;
|
|
@@ -529,7 +532,7 @@ export function notify(options = {}) {
|
|
|
529
532
|
el._onClose = options.onClose;
|
|
530
533
|
t.append(el);
|
|
531
534
|
// Enter from the edge: start off-screen, then let layout() transition it in.
|
|
532
|
-
const top = (t.position ?? "bottom-
|
|
535
|
+
const top = (t.position ?? "bottom-end").startsWith("top");
|
|
533
536
|
el.style.transition = "none";
|
|
534
537
|
el.style.opacity = "0";
|
|
535
538
|
el.style.transform = `translateY(${top ? -100 : 100}%)`;
|
|
@@ -115,7 +115,7 @@ export class RayNumberInput extends FieldControl {
|
|
|
115
115
|
static styles = [sizeStyles, radiusStyles, fieldStyles, boxStyles, styles];
|
|
116
116
|
static template = fieldTemplate(
|
|
117
117
|
`<input id="control" class="control" part="input" role="spinbutton" inputmode="decimal" autocomplete="off" spellcheck="false">` +
|
|
118
|
-
`<span class="steppers" part="controls"><button type="button" class="step up" part="increment" tabindex="-1" aria-label="Increment"><span data-ray-icon="
|
|
118
|
+
`<span class="steppers" part="controls"><button type="button" class="step up" part="increment" tabindex="-1" aria-label="Increment"><span data-ray-icon="ui.increment"></span></button><button type="button" class="step down" part="decrement" tabindex="-1" aria-label="Decrement"><span data-ray-icon="ui.decrement"></span></button></span>`,
|
|
119
119
|
);
|
|
120
120
|
|
|
121
121
|
#typing = false;
|
|
@@ -139,15 +139,15 @@ export class RayPagination extends RayElement {
|
|
|
139
139
|
const items = [];
|
|
140
140
|
const control = (role, target, icon, label, off) => ({ kind: "control", role, target, icon, label, off });
|
|
141
141
|
if (!this.hideControls) {
|
|
142
|
-
if (this.withEdges) items.push(control("first", 1, "
|
|
143
|
-
items.push(control("previous", page - 1, "
|
|
142
|
+
if (this.withEdges) items.push(control("first", 1, "ui.first", "First page", page <= 1));
|
|
143
|
+
items.push(control("previous", page - 1, "ui.previous", "Previous page", page <= 1));
|
|
144
144
|
}
|
|
145
145
|
for (const it of paginationRange(total, page, this.siblings ?? 1, this.boundaries ?? 1)) {
|
|
146
146
|
items.push(it === "dots" ? { kind: "dots" } : { kind: "page", target: it });
|
|
147
147
|
}
|
|
148
148
|
if (!this.hideControls) {
|
|
149
|
-
items.push(control("next", page + 1, "
|
|
150
|
-
if (this.withEdges) items.push(control("last", total, "
|
|
149
|
+
items.push(control("next", page + 1, "ui.next", "Next page", page >= total));
|
|
150
|
+
if (this.withEdges) items.push(control("last", total, "ui.last", "Last page", page >= total));
|
|
151
151
|
}
|
|
152
152
|
list.replaceChildren(...items.map((it) => this._item(it, page)));
|
|
153
153
|
this.paintIcons(list);
|
|
@@ -43,7 +43,7 @@ export class RayPasswordInput extends RayInput {
|
|
|
43
43
|
static styles = [...RayInput.styles, styles];
|
|
44
44
|
static template = fieldTemplate(
|
|
45
45
|
`<input id="control" class="control" part="input" type="password" autocomplete="current-password" spellcheck="false" autocapitalize="off">` +
|
|
46
|
-
`<button type="button" class="toggle focus-ring" part="toggle" aria-pressed="false"><span data-ray-icon="
|
|
46
|
+
`<button type="button" class="toggle focus-ring" part="toggle" aria-pressed="false"><span data-ray-icon="ui.show-password" aria-hidden="true"></span></button>`,
|
|
47
47
|
);
|
|
48
48
|
|
|
49
49
|
wire() {
|
|
@@ -69,7 +69,7 @@ export class RayPasswordInput extends RayInput {
|
|
|
69
69
|
toggle.title = toggle.getAttribute("aria-label");
|
|
70
70
|
toggle.disabled = this.effectivelyDisabled;
|
|
71
71
|
const icon = toggle.firstElementChild;
|
|
72
|
-
const name = this.visible ? "
|
|
72
|
+
const name = this.visible ? "ui.hide-password" : "ui.show-password";
|
|
73
73
|
if (icon.dataset.rayIcon !== name) {
|
|
74
74
|
icon.dataset.rayIcon = name;
|
|
75
75
|
this.paintIcons(toggle);
|
|
@@ -42,7 +42,7 @@ const styles = css`
|
|
|
42
42
|
.value {
|
|
43
43
|
flex: none;
|
|
44
44
|
min-width: 2.75em;
|
|
45
|
-
text-align:
|
|
45
|
+
text-align: end;
|
|
46
46
|
font-size: var(--ray-font-size-xs);
|
|
47
47
|
font-variant-numeric: tabular-nums;
|
|
48
48
|
color: var(--ray-muted-foreground);
|
|
@@ -59,7 +59,7 @@ const styles = css`
|
|
|
59
59
|
width: 40%;
|
|
60
60
|
animation: ray-indeterminate 1.4s var(--ray-easing-standard) infinite;
|
|
61
61
|
}
|
|
62
|
-
@keyframes ray-indeterminate { from {
|
|
62
|
+
@keyframes ray-indeterminate { from { inset-inline-start: -40%; } to { inset-inline-start: 100%; } }
|
|
63
63
|
`;
|
|
64
64
|
|
|
65
65
|
export class RayProgress extends RayElement {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// plans, presets, shipping methods. Both can share a group.
|
|
9
9
|
|
|
10
10
|
import { RayElement, css, define } from "../../core/core.js";
|
|
11
|
+
import { inlineKey } from "../../core/direction.js";
|
|
11
12
|
import { applyColor, sizeStyles } from "../../core/styles.js";
|
|
12
13
|
import { FormControl, fieldStyles } from "../../core/form.js";
|
|
13
14
|
|
|
@@ -136,9 +137,10 @@ export class RayRadioGroup extends FormControl {
|
|
|
136
137
|
const radios = this.radios.filter((r) => !r.disabled);
|
|
137
138
|
const i = radios.indexOf(e.target.closest?.(RADIOS));
|
|
138
139
|
if (i < 0) return;
|
|
140
|
+
const key = inlineKey(e.key, this);
|
|
139
141
|
let next = null;
|
|
140
|
-
if (
|
|
141
|
-
else if (
|
|
142
|
+
if (key === "ArrowDown" || key === "ArrowRight") next = radios[(i + 1) % radios.length];
|
|
143
|
+
else if (key === "ArrowUp" || key === "ArrowLeft") next = radios[(i - 1 + radios.length) % radios.length];
|
|
142
144
|
else if (e.key === " ") next = radios[i];
|
|
143
145
|
if (!next) return;
|
|
144
146
|
e.preventDefault();
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Give it a size: height / max-height (or CSS, or a flex/grid parent).
|
|
8
8
|
|
|
9
9
|
import { RayElement, css, define } from "../../core/core.js";
|
|
10
|
+
import { isRtl } from "../../core/direction.js";
|
|
10
11
|
|
|
11
12
|
const styles = css`
|
|
12
13
|
:host {
|
|
@@ -47,12 +48,14 @@ const styles = css`
|
|
|
47
48
|
transition: opacity var(--ray-duration-fast);
|
|
48
49
|
}
|
|
49
50
|
.edge[data-show] { opacity: 1; }
|
|
50
|
-
.top, .bottom {
|
|
51
|
-
.
|
|
51
|
+
.top, .bottom { inset-inline: 0; height: 0.75rem; }
|
|
52
|
+
.start, .end { top: 0; bottom: 0; width: 0.75rem; }
|
|
52
53
|
.top { top: 0; background: linear-gradient(var(--_shadow), transparent); }
|
|
53
54
|
.bottom { bottom: 0; background: linear-gradient(transparent, var(--_shadow)); }
|
|
54
|
-
.
|
|
55
|
-
.
|
|
55
|
+
.start { inset-inline-start: 0; background: linear-gradient(to right, var(--_shadow), transparent); } /* physical */
|
|
56
|
+
.end { inset-inline-end: 0; background: linear-gradient(to left, var(--_shadow), transparent); } /* physical */
|
|
57
|
+
.start:dir(rtl) { background: linear-gradient(to left, var(--_shadow), transparent); } /* physical */
|
|
58
|
+
.end:dir(rtl) { background: linear-gradient(to right, var(--_shadow), transparent); } /* physical */
|
|
56
59
|
:host([no-shadows]) .edge { display: none; }
|
|
57
60
|
`;
|
|
58
61
|
|
|
@@ -70,7 +73,7 @@ export class RayScrollArea extends RayElement {
|
|
|
70
73
|
static template = `
|
|
71
74
|
<div class="viewport" part="viewport" tabindex="0"><slot></slot></div>
|
|
72
75
|
<span class="edge top" part="shadow"></span><span class="edge bottom" part="shadow"></span>
|
|
73
|
-
<span class="edge
|
|
76
|
+
<span class="edge start" part="shadow"></span><span class="edge end" part="shadow"></span>`;
|
|
74
77
|
|
|
75
78
|
#ro = null;
|
|
76
79
|
#atEnd = false;
|
|
@@ -115,15 +118,15 @@ export class RayScrollArea extends RayElement {
|
|
|
115
118
|
this._edges();
|
|
116
119
|
}
|
|
117
120
|
|
|
118
|
-
/** Scrolls to the start (top
|
|
121
|
+
/** Scrolls to the start (the top, or the left; the right right to left). */
|
|
119
122
|
scrollToStart({ behavior = "smooth" } = {}) {
|
|
120
123
|
this.viewport.scrollTo({ top: 0, left: 0, behavior });
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
/** Scrolls to the end (bottom, or right when horizontal). */
|
|
126
|
+
/** Scrolls to the end (the bottom, or the right when horizontal; the left right to left). */
|
|
124
127
|
scrollToEnd({ behavior = "smooth" } = {}) {
|
|
125
128
|
const v = this.viewport;
|
|
126
|
-
if (this.orientation === "horizontal") v.scrollTo({ left: v.scrollWidth, behavior });
|
|
129
|
+
if (this.orientation === "horizontal") v.scrollTo({ left: isRtl(this) ? -v.scrollWidth : v.scrollWidth, behavior });
|
|
127
130
|
else v.scrollTo({ top: v.scrollHeight, behavior });
|
|
128
131
|
}
|
|
129
132
|
|
|
@@ -144,13 +147,13 @@ export class RayScrollArea extends RayElement {
|
|
|
144
147
|
const x = Math.abs(v.scrollLeft);
|
|
145
148
|
show("top", v.scrollTop > 1);
|
|
146
149
|
show("bottom", maxY > 1 && v.scrollTop < maxY - 1);
|
|
147
|
-
show("
|
|
148
|
-
show("
|
|
150
|
+
show("start", x > 1);
|
|
151
|
+
show("end", maxX > 1 && x < maxX - 1);
|
|
149
152
|
const horizontal = this.orientation === "horizontal";
|
|
150
153
|
const left = horizontal ? maxX - x : maxY - v.scrollTop;
|
|
151
154
|
const scrollable = horizontal ? maxX > 1 : maxY > 1;
|
|
152
155
|
const atEnd = scrollable && left <= (this.threshold ?? 48);
|
|
153
|
-
if (atEnd && !this.#atEnd) this.emit("ray-reach-end", { edge: horizontal ? "
|
|
156
|
+
if (atEnd && !this.#atEnd) this.emit("ray-reach-end", { edge: !horizontal ? "bottom" : isRtl(this) ? "left" : "right" });
|
|
154
157
|
this.#atEnd = atEnd;
|
|
155
158
|
}
|
|
156
159
|
}
|
|
@@ -14,14 +14,14 @@ const styles = css`
|
|
|
14
14
|
:host(:not([variant])) .box { background: var(--ray-muted); border-color: transparent; }
|
|
15
15
|
.lead {
|
|
16
16
|
display: inline-flex;
|
|
17
|
-
padding-
|
|
17
|
+
padding-inline-start: calc(var(--_px) * 0.66);
|
|
18
18
|
color: var(--ray-muted-foreground);
|
|
19
19
|
pointer-events: none;
|
|
20
20
|
}
|
|
21
21
|
.lead svg { width: 1.1em; height: 1.1em; }
|
|
22
|
-
.control { padding-
|
|
22
|
+
.control { padding-inline-start: calc(var(--_px) * 0.5); }
|
|
23
23
|
.control::-webkit-search-cancel-button, .control::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
|
|
24
|
-
.tail { display: inline-flex; align-items: center; gap: var(--ray-space-1); padding-
|
|
24
|
+
.tail { display: inline-flex; align-items: center; gap: var(--ray-space-1); padding-inline-end: calc(var(--_px) * 0.4); }
|
|
25
25
|
.clear {
|
|
26
26
|
display: inline-flex;
|
|
27
27
|
align-items: center;
|
|
@@ -62,9 +62,9 @@ export class RaySearch extends RayInput {
|
|
|
62
62
|
};
|
|
63
63
|
static styles = [...RayInput.styles, styles];
|
|
64
64
|
static template = fieldTemplate(
|
|
65
|
-
`<span class="lead" part="icon" aria-hidden="true" data-ray-icon="search"></span>` +
|
|
65
|
+
`<span class="lead" part="icon" aria-hidden="true" data-ray-icon="ui.search"></span>` +
|
|
66
66
|
`<input id="control" class="control" part="input" type="search" enterkeyhint="search" autocomplete="off" spellcheck="false">` +
|
|
67
|
-
`<span class="tail"><button class="clear focus-ring" part="clear" type="button" aria-label="Clear search" tabindex="-1" hidden><span data-ray-icon="
|
|
67
|
+
`<span class="tail"><button class="clear focus-ring" part="clear" type="button" aria-label="Clear search" tabindex="-1" hidden><span data-ray-icon="ui.clear" aria-hidden="true"></span></button>` +
|
|
68
68
|
`<kbd class="shortcut" part="shortcut" aria-hidden="true" hidden></kbd></span>`,
|
|
69
69
|
);
|
|
70
70
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// <option> elements or the `data` property. Form-associated.
|
|
4
4
|
|
|
5
5
|
import { css, define } from "../../core/core.js";
|
|
6
|
+
import { inlineKey } from "../../core/direction.js";
|
|
6
7
|
import { applyColor, sizeStyles, radiusStyles } from "../../core/styles.js";
|
|
7
8
|
import { FormControl } from "../../core/form.js";
|
|
8
9
|
|
|
@@ -22,7 +23,7 @@ const styles = css`
|
|
|
22
23
|
.indicator {
|
|
23
24
|
position: absolute;
|
|
24
25
|
top: 0;
|
|
25
|
-
left: 0;
|
|
26
|
+
left: 0; /* physical */
|
|
26
27
|
z-index: 0;
|
|
27
28
|
box-sizing: border-box;
|
|
28
29
|
background: var(--ray-raised);
|
|
@@ -170,9 +171,10 @@ export class RaySegmentedControl extends FormControl {
|
|
|
170
171
|
_key(e) {
|
|
171
172
|
const enabled = this._buttons.filter((b) => !b.disabled);
|
|
172
173
|
const i = enabled.findIndex((b) => b.dataset.value === this.value);
|
|
174
|
+
const key = inlineKey(e.key, this);
|
|
173
175
|
let next = null;
|
|
174
|
-
if (
|
|
175
|
-
else if (
|
|
176
|
+
if (key === "ArrowRight" || key === "ArrowDown") next = enabled[(i + 1) % enabled.length];
|
|
177
|
+
else if (key === "ArrowLeft" || key === "ArrowUp") next = enabled[(i - 1 + enabled.length) % enabled.length];
|
|
176
178
|
if (!next) return;
|
|
177
179
|
e.preventDefault();
|
|
178
180
|
this._choose(next.dataset.value, true);
|
|
@@ -30,8 +30,8 @@ const styles = css`
|
|
|
30
30
|
align-items: center;
|
|
31
31
|
width: 100%;
|
|
32
32
|
min-width: 0;
|
|
33
|
-
padding-
|
|
34
|
-
text-align:
|
|
33
|
+
padding-inline-end: calc(var(--_h) * 0.8);
|
|
34
|
+
text-align: start;
|
|
35
35
|
color: inherit;
|
|
36
36
|
background: transparent;
|
|
37
37
|
border: 0;
|
|
@@ -43,7 +43,7 @@ const styles = css`
|
|
|
43
43
|
.trigger[data-placeholder] .value { color: var(--ray-muted-foreground); }
|
|
44
44
|
.chevron {
|
|
45
45
|
position: absolute;
|
|
46
|
-
|
|
46
|
+
inset-inline-end: 0;
|
|
47
47
|
display: inline-flex;
|
|
48
48
|
align-items: center;
|
|
49
49
|
justify-content: center;
|
|
@@ -101,7 +101,7 @@ const styles = css`
|
|
|
101
101
|
.empty { padding: var(--ray-space-2); color: var(--ray-muted-foreground); }
|
|
102
102
|
`;
|
|
103
103
|
|
|
104
|
-
const CHEVRON = `<span class="chevron" aria-hidden="true" data-ray-icon="
|
|
104
|
+
const CHEVRON = `<span class="chevron" aria-hidden="true" data-ray-icon="ui.expand"></span>`;
|
|
105
105
|
|
|
106
106
|
export class RaySelect extends FieldControl {
|
|
107
107
|
static properties = {
|
|
@@ -261,7 +261,7 @@ export class RaySelect extends FieldControl {
|
|
|
261
261
|
const group = o.parentElement?.localName === "optgroup" ? o.parentElement : null;
|
|
262
262
|
const disabled = o.disabled || Boolean(group?.disabled);
|
|
263
263
|
el.setAttribute("aria-disabled", String(disabled));
|
|
264
|
-
el.innerHTML = `<span class="text"></span><span class="mark" aria-hidden="true" data-ray-icon="
|
|
264
|
+
el.innerHTML = `<span class="text"></span><span class="mark" aria-hidden="true" data-ray-icon="ui.checked"></span>`;
|
|
265
265
|
el.firstChild.textContent = o.label || o.text;
|
|
266
266
|
this.#items.push({ el, value: o.value, disabled, text: (o.label || o.text).trim().toLowerCase() });
|
|
267
267
|
return el;
|