@raylang/elements 0.7.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/components/accordion/accordion.js +7 -4
- package/components/alert/alert.js +2 -1
- package/components/attachment/attachment.js +4 -5
- package/components/blockquote/blockquote.js +2 -2
- package/components/bottom-nav/bottom-nav.js +3 -3
- package/components/carousel/carousel.js +22 -10
- package/components/checkbox/checkbox.js +5 -2
- package/components/checkbox-group/checkbox-group.js +1 -1
- package/components/code/code.js +8 -6
- package/components/color-picker/color-picker.js +5 -5
- package/components/combobox/combobox.js +2 -1
- package/components/command-palette/command-palette.js +1 -1
- package/components/date-picker/date-picker.js +5 -3
- package/components/dialog/dialog.js +1 -1
- package/components/drawer/drawer.js +27 -9
- package/components/icon/icon.js +4 -1
- package/components/indicator/indicator.js +2 -1
- package/components/menu/menu.js +1 -1
- package/components/nav-link/nav-link.js +5 -2
- package/components/notification/notification.js +15 -12
- 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 +3 -3
- package/components/segmented-control/segmented-control.js +5 -3
- package/components/select/select.js +3 -3
- 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/stepper/stepper.js +4 -4
- package/components/switch/switch.js +3 -3
- package/components/table/table.js +5 -5
- package/components/tabs/tabs.js +11 -8
- package/components/timeline/timeline.js +13 -8
- package/components/toggle-group/toggle-group.js +6 -4
- package/core/core.js +6 -1
- package/core/direction.js +19 -0
- package/core/icons.js +12 -0
- package/core/position.js +11 -3
- package/core/styles.js +1 -1
- package/core.js +1 -0
- package/custom-elements.json +80 -62
- package/llms-full.txt +41 -17
- package/llms.txt +20 -2
- package/package.json +1 -1
- package/ray-elements.d.ts +32 -25
- package/vscode.html-custom-data.json +80 -18
|
@@ -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;
|
|
@@ -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;
|
|
@@ -18,7 +18,7 @@ const styles = css`
|
|
|
18
18
|
:host([variant="dotted"]) { --_style: dotted; }
|
|
19
19
|
:host([orientation="vertical"]) { display: inline-block; align-self: stretch; height: auto; min-height: 1em; vertical-align: middle; }
|
|
20
20
|
.line { border-top: var(--_w) var(--_style) var(--_color); }
|
|
21
|
-
:host([orientation="vertical"]) .line { height: 100%; border-top: 0; border-
|
|
21
|
+
:host([orientation="vertical"]) .line { height: 100%; border-top: 0; border-inline-start: var(--_w) var(--_style) var(--_color); }
|
|
22
22
|
.labeled {
|
|
23
23
|
display: flex;
|
|
24
24
|
align-items: center;
|
|
@@ -27,8 +27,13 @@ const styles = css`
|
|
|
27
27
|
color: var(--ray-muted-foreground);
|
|
28
28
|
}
|
|
29
29
|
.labeled::before, .labeled::after { content: ""; flex: 1; border-top: var(--_w) var(--_style) var(--_color); }
|
|
30
|
-
|
|
31
|
-
:host([label-position="
|
|
30
|
+
/* start/end follow the writing direction; left/right are sides of the screen. */
|
|
31
|
+
:host([label-position="start"]) .labeled::before,
|
|
32
|
+
:host([label-position="left"]) .labeled:dir(ltr)::before,
|
|
33
|
+
:host([label-position="right"]) .labeled:dir(rtl)::before,
|
|
34
|
+
:host([label-position="end"]) .labeled::after,
|
|
35
|
+
:host([label-position="right"]) .labeled:dir(ltr)::after,
|
|
36
|
+
:host([label-position="left"]) .labeled:dir(rtl)::after { flex: 0 0 var(--ray-space-3); }
|
|
32
37
|
[hidden] { display: none !important; }
|
|
33
38
|
`;
|
|
34
39
|
|
|
@@ -38,7 +38,11 @@ const styles = css`
|
|
|
38
38
|
background: var(--ray-sidebar, color-mix(in srgb, var(--ray-muted) 70%, var(--ray-background)));
|
|
39
39
|
border-inline-end: 1px solid var(--ray-border);
|
|
40
40
|
}
|
|
41
|
-
:
|
|
41
|
+
/* side: start/end follow the writing direction, left/right are sides of
|
|
42
|
+
the screen; the border faces the content. */
|
|
43
|
+
:host([side="end"]) .panel { border-inline-end: 0; border-inline-start: 1px solid var(--ray-border); }
|
|
44
|
+
:host([side="left"]) .panel { border: 0; border-right: 1px solid var(--ray-border); } /* physical */
|
|
45
|
+
:host([side="right"]) .panel { border: 0; border-left: 1px solid var(--ray-border); } /* physical */
|
|
42
46
|
:host([variant="floating"]:not([data-mobile])) { padding: var(--ray-space-2); box-sizing: border-box; }
|
|
43
47
|
:host([variant="floating"]) .panel { border: 1px solid var(--ray-border); border-radius: var(--ray-radius-lg); box-shadow: var(--ray-shadow-sm); }
|
|
44
48
|
.header, .footer { flex: none; padding: var(--ray-space-2); }
|
|
@@ -49,7 +53,8 @@ const styles = css`
|
|
|
49
53
|
dialog {
|
|
50
54
|
--_from: -100%;
|
|
51
55
|
position: fixed;
|
|
52
|
-
inset: 0
|
|
56
|
+
inset-block: 0;
|
|
57
|
+
inset-inline: 0 auto;
|
|
53
58
|
box-sizing: border-box;
|
|
54
59
|
width: min(var(--_w), calc(100vw - 3rem));
|
|
55
60
|
max-width: none;
|
|
@@ -61,11 +66,14 @@ const styles = css`
|
|
|
61
66
|
background: transparent;
|
|
62
67
|
overflow: visible;
|
|
63
68
|
}
|
|
64
|
-
:
|
|
69
|
+
dialog:dir(rtl) { --_from: 100%; }
|
|
70
|
+
:host([side="end"]) dialog { inset-inline: auto 0; --_from: 100%; }
|
|
71
|
+
:host([side="end"]) dialog:dir(rtl) { --_from: -100%; }
|
|
72
|
+
:host([side="left"]) dialog { inset: 0 auto 0 0; --_from: -100%; } /* physical */
|
|
73
|
+
:host([side="right"]) dialog { inset: 0 0 0 auto; --_from: 100%; } /* physical */
|
|
65
74
|
dialog[open] { display: block; animation: ray-sidebar-in var(--ray-duration-slow) var(--ray-easing-out); }
|
|
66
75
|
dialog .panel { box-shadow: var(--ray-shadow-xl); background: var(--ray-card); }
|
|
67
76
|
@keyframes ray-sidebar-in { from { transform: translateX(var(--_from)); } }
|
|
68
|
-
:host([side="right"]) dialog { --_from: 100%; }
|
|
69
77
|
dialog::backdrop { animation: ray-fade-in var(--ray-duration-base) var(--ray-easing-out); }
|
|
70
78
|
@keyframes ray-fade-in { from { opacity: 0; } }
|
|
71
79
|
`;
|
|
@@ -74,7 +82,7 @@ export class RaySidebar extends RayElement {
|
|
|
74
82
|
static properties = {
|
|
75
83
|
collapsed: { type: Boolean, reflect: true },
|
|
76
84
|
open: { type: Boolean, reflect: true },
|
|
77
|
-
side: { type: String, reflect: true, default: "
|
|
85
|
+
side: { type: String, reflect: true, default: "start" },
|
|
78
86
|
variant: { type: String, reflect: true, default: "sidebar" },
|
|
79
87
|
width: { type: String },
|
|
80
88
|
collapsedWidth: { type: String },
|
|
@@ -193,7 +201,7 @@ export class RaySidebar extends RayElement {
|
|
|
193
201
|
}
|
|
194
202
|
|
|
195
203
|
async _leave() {
|
|
196
|
-
const to = this.
|
|
204
|
+
const to = `translateX(${getComputedStyle(this.dialog).getPropertyValue("--_from").trim() || "-100%"})`;
|
|
197
205
|
const exit = playAll([
|
|
198
206
|
play(this.dialog, [{ transform: "none" }, { transform: to }]),
|
|
199
207
|
play(this.dialog, [{ opacity: 1 }, { opacity: 0 }], { pseudoElement: "::backdrop" }),
|
|
@@ -33,9 +33,11 @@ const styles = css`
|
|
|
33
33
|
input::-webkit-slider-runnable-track {
|
|
34
34
|
height: var(--_track);
|
|
35
35
|
border-radius: var(--ray-radius-full);
|
|
36
|
-
background: linear-gradient(to right, var(--_filled) var(--_pct), var(--ray-muted) var(--_pct));
|
|
36
|
+
background: linear-gradient(to right, var(--_filled) var(--_pct), var(--ray-muted) var(--_pct)); /* physical */
|
|
37
37
|
box-shadow: inset 0 0 0 1px var(--ray-border);
|
|
38
38
|
}
|
|
39
|
+
/* Right to left the native range runs from the right: so does its fill. */
|
|
40
|
+
input:dir(rtl)::-webkit-slider-runnable-track { background: linear-gradient(to left, var(--_filled) var(--_pct), var(--ray-muted) var(--_pct)); } /* physical */
|
|
39
41
|
input::-moz-range-track { height: var(--_track); border-radius: var(--ray-radius-full); background: var(--ray-muted); }
|
|
40
42
|
input::-moz-range-progress { height: var(--_track); border-radius: var(--ray-radius-full); background: var(--_filled); }
|
|
41
43
|
input::-webkit-slider-thumb {
|
|
@@ -62,6 +64,7 @@ const styles = css`
|
|
|
62
64
|
.marks { position: relative; height: 1.25rem; margin: var(--ray-space-1) calc(var(--_thumb) / 2) 0; font-size: var(--ray-font-size-xs); color: var(--ray-muted-foreground); }
|
|
63
65
|
.marks:empty { display: none; }
|
|
64
66
|
.mark { position: absolute; transform: translateX(-50%); white-space: nowrap; }
|
|
67
|
+
.mark:dir(rtl) { transform: translateX(50%); }
|
|
65
68
|
.mark::before { content: ""; display: block; width: 1px; height: 0.25rem; margin: 0 auto 0.125rem; background: var(--ray-border); }
|
|
66
69
|
`;
|
|
67
70
|
|
|
@@ -137,7 +140,7 @@ export class RaySlider extends FieldControl {
|
|
|
137
140
|
const v = typeof m === "number" ? m : m.value;
|
|
138
141
|
const span = document.createElement("span");
|
|
139
142
|
span.className = "mark";
|
|
140
|
-
span.style.
|
|
143
|
+
span.style.insetInlineStart = `${((v - min) / (max - min || 1)) * 100}%`;
|
|
141
144
|
span.textContent = typeof m === "number" ? String(m) : (m.label ?? String(m.value));
|
|
142
145
|
box.append(span);
|
|
143
146
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// pane. With `autosave="key"` the size survives reloads (localStorage).
|
|
8
8
|
|
|
9
9
|
import { RayElement, css, define } from "../../core/core.js";
|
|
10
|
+
import { isRtl, inlineKey } from "../../core/direction.js";
|
|
10
11
|
|
|
11
12
|
const styles = css`
|
|
12
13
|
:host {
|
|
@@ -34,13 +35,13 @@ const styles = css`
|
|
|
34
35
|
position: absolute;
|
|
35
36
|
top: 0;
|
|
36
37
|
bottom: 0;
|
|
37
|
-
left: 50%;
|
|
38
|
+
left: 50%; /* physical */
|
|
38
39
|
width: 1px;
|
|
39
40
|
background: var(--ray-border);
|
|
40
41
|
transform: translateX(-50%);
|
|
41
42
|
transition: background-color var(--ray-duration-fast) var(--ray-easing-standard), width var(--ray-duration-fast) var(--ray-easing-standard);
|
|
42
43
|
}
|
|
43
|
-
:host([orientation="vertical"]) .divider::before { top: 50%; bottom: auto;
|
|
44
|
+
:host([orientation="vertical"]) .divider::before { top: 50%; bottom: auto; inset-inline: 0; width: auto; height: 1px; transform: translateY(-50%); }
|
|
44
45
|
:host(:not([disabled])) .divider:hover::before,
|
|
45
46
|
.divider:focus-visible::before,
|
|
46
47
|
:host([data-dragging]) .divider::before { width: 3px; background: var(--ray-primary); }
|
|
@@ -182,13 +183,16 @@ export class RaySplit extends RayElement {
|
|
|
182
183
|
this.setAttribute("data-dragging", "");
|
|
183
184
|
const rect = this.getBoundingClientRect();
|
|
184
185
|
const vertical = this._vertical();
|
|
185
|
-
|
|
186
|
+
// The start pane's size: right to left it is on the right.
|
|
187
|
+
const rtl = !vertical && isRtl(this);
|
|
188
|
+
const at = (ev) => (vertical ? ev.clientY - rect.top : rtl ? rect.right - ev.clientX : ev.clientX - rect.left);
|
|
189
|
+
const move = (ev) => this._resizeTo(at(ev), false);
|
|
186
190
|
const up = (ev) => {
|
|
187
191
|
this.divider.removeEventListener("pointermove", move);
|
|
188
192
|
this.divider.removeEventListener("pointerup", up);
|
|
189
193
|
this.divider.removeEventListener("pointercancel", up);
|
|
190
194
|
this.removeAttribute("data-dragging");
|
|
191
|
-
this._resizeTo(
|
|
195
|
+
this._resizeTo(at(ev), true);
|
|
192
196
|
};
|
|
193
197
|
this.divider.addEventListener("pointermove", move);
|
|
194
198
|
this.divider.addEventListener("pointerup", up);
|
|
@@ -202,9 +206,11 @@ export class RaySplit extends RayElement {
|
|
|
202
206
|
const current = this.collapsed ? 0 : this.size;
|
|
203
207
|
const less = vertical ? "ArrowUp" : "ArrowLeft";
|
|
204
208
|
const more = vertical ? "ArrowDown" : "ArrowRight";
|
|
209
|
+
// Right to left, ArrowLeft moves the divider away from the start pane.
|
|
210
|
+
const key = vertical ? e.key : inlineKey(e.key, this);
|
|
205
211
|
let next = null;
|
|
206
|
-
if (
|
|
207
|
-
else if (
|
|
212
|
+
if (key === less) next = current - step;
|
|
213
|
+
else if (key === more) next = current + step;
|
|
208
214
|
else if (e.key === "Home") next = this.min ?? 0;
|
|
209
215
|
else if (e.key === "End") next = this.max ?? this._total();
|
|
210
216
|
else if (e.key === "Enter") {
|
|
@@ -30,7 +30,7 @@ const stepperStyles = css`
|
|
|
30
30
|
padding: 0;
|
|
31
31
|
font: inherit;
|
|
32
32
|
color: inherit;
|
|
33
|
-
text-align:
|
|
33
|
+
text-align: start;
|
|
34
34
|
background: none;
|
|
35
35
|
border: 0;
|
|
36
36
|
border-radius: var(--ray-radius-md);
|
|
@@ -58,7 +58,7 @@ const stepperStyles = css`
|
|
|
58
58
|
.step[data-state="active"] .indicator { color: var(--_light-color); background: var(--ray-background); border-color: var(--_filled); }
|
|
59
59
|
.step[data-state="done"] .indicator { color: var(--_contrast); background: var(--_filled); border-color: var(--_filled); }
|
|
60
60
|
.step[data-error] .indicator { color: #fff; background: var(--ray-red-filled); border-color: var(--ray-red-filled); }
|
|
61
|
-
.spinner { width: 50%; height: 50%; border: 2px solid currentColor; border-
|
|
61
|
+
.spinner { width: 50%; height: 50%; border: 2px solid currentColor; border-inline-end-color: transparent; border-radius: 50%; animation: ray-step-spin 0.8s linear infinite; }
|
|
62
62
|
@keyframes ray-step-spin { to { rotate: 1turn; } }
|
|
63
63
|
.text { display: grid; min-width: 0; }
|
|
64
64
|
.label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--ray-font-size-sm); font-weight: var(--ray-font-weight-medium); }
|
|
@@ -69,7 +69,7 @@ const stepperStyles = css`
|
|
|
69
69
|
.connector { flex: 1; min-width: var(--ray-space-4); height: 2px; border-radius: 1px; background: var(--ray-border); transition: background-color var(--ray-duration-base); }
|
|
70
70
|
.step[data-state="done"] .connector { background: var(--_filled); }
|
|
71
71
|
.step:last-child .connector { display: none; }
|
|
72
|
-
:host([orientation="vertical"]) .connector { flex: none; width: 2px; min-width: 0; height: auto; min-height: var(--ray-space-6); margin: var(--ray-space-1)
|
|
72
|
+
:host([orientation="vertical"]) .connector { flex: none; width: 2px; min-width: 0; height: auto; min-height: var(--ray-space-6); margin-block: var(--ray-space-1); margin-inline-start: calc(var(--_icon) / 2 - 1px); }
|
|
73
73
|
/* Narrow (labels-below or a narrow stepper): the labels under the numbers. */
|
|
74
74
|
:host([icon-position="top"]) .step { align-items: flex-start; }
|
|
75
75
|
:host([icon-position="top"]) .head { flex-direction: column; align-items: center; text-align: center; gap: var(--ray-space-1); }
|
|
@@ -91,7 +91,7 @@ export class RayStepper extends RayElement {
|
|
|
91
91
|
static properties = {
|
|
92
92
|
active: { type: Number, reflect: true, default: 0 },
|
|
93
93
|
orientation: { type: String, reflect: true, default: "horizontal" },
|
|
94
|
-
iconPosition: { type: String, reflect: true, attribute: "icon-position", default: "
|
|
94
|
+
iconPosition: { type: String, reflect: true, attribute: "icon-position", default: "start" },
|
|
95
95
|
allowStepSelect: { type: Boolean, reflect: true, attribute: "allow-step-select" },
|
|
96
96
|
linear: { type: Boolean, default: true },
|
|
97
97
|
size: { type: String, reflect: true, default: "md" },
|
|
@@ -18,7 +18,7 @@ const styles = css`
|
|
|
18
18
|
.thumb {
|
|
19
19
|
position: absolute;
|
|
20
20
|
top: 50%;
|
|
21
|
-
|
|
21
|
+
inset-inline-start: 2px;
|
|
22
22
|
width: calc(var(--_s) - 6px);
|
|
23
23
|
height: calc(var(--_s) - 6px);
|
|
24
24
|
background: var(--ray-white);
|
|
@@ -26,12 +26,12 @@ const styles = css`
|
|
|
26
26
|
border-radius: var(--_r);
|
|
27
27
|
box-shadow: var(--ray-shadow-xs);
|
|
28
28
|
transform: translateY(-50%);
|
|
29
|
-
transition:
|
|
29
|
+
transition: inset-inline-start var(--ray-duration-base) var(--ray-easing-standard);
|
|
30
30
|
}
|
|
31
31
|
.control:checked + .indicator { background: var(--_filled); border-color: var(--_filled); }
|
|
32
32
|
/* The thumb takes the text color of the filled shade: white on dark fills,
|
|
33
33
|
black on light ones (yellow, or "dark" in the dark scheme). */
|
|
34
|
-
.control:checked + .indicator .thumb {
|
|
34
|
+
.control:checked + .indicator .thumb { inset-inline-start: calc(100% - var(--_s) + 4px); border-color: transparent; background: var(--_contrast); }
|
|
35
35
|
/* Disabled: a muted track (gray when on, like a disabled checkbox) and a
|
|
36
36
|
plain thumb that stays visible on it, on its side. */
|
|
37
37
|
.control:disabled + .indicator .thumb { background: var(--ray-background); border-color: var(--ray-border); box-shadow: none; }
|
|
@@ -27,14 +27,14 @@ const styles = css`
|
|
|
27
27
|
.scroll { max-height: var(--ray-table-max-height, none); overflow: auto; }
|
|
28
28
|
:host([with-border]) .scroll { border: 1px solid var(--ray-border); border-radius: var(--ray-radius-default); }
|
|
29
29
|
table { width: 100%; border-collapse: separate; border-spacing: 0; font-variant-numeric: tabular-nums; }
|
|
30
|
-
caption { padding: var(--_py) var(--_px); text-align:
|
|
31
|
-
th, td { padding: var(--_py) var(--_px); text-align:
|
|
32
|
-
:host([with-column-borders]) th + th, :host([with-column-borders]) td + td { border-
|
|
30
|
+
caption { padding: var(--_py) var(--_px); text-align: start; font-weight: var(--ray-font-weight-semibold); }
|
|
31
|
+
th, td { padding: var(--_py) var(--_px); text-align: start; vertical-align: middle; border-bottom: 1px solid var(--ray-border); }
|
|
32
|
+
:host([with-column-borders]) th + th, :host([with-column-borders]) td + td { border-inline-start: 1px solid var(--ray-border); }
|
|
33
33
|
tbody tr:last-child td { border-bottom: 0; }
|
|
34
34
|
:host(:not([with-border])) tbody tr:last-child td { border-bottom: 1px solid var(--ray-border); }
|
|
35
35
|
th { font-size: var(--ray-font-size-xs); font-weight: var(--ray-font-weight-semibold); color: var(--ray-muted-foreground); white-space: nowrap; background: var(--ray-background); }
|
|
36
36
|
:host([sticky-header]) thead th { position: sticky; top: 0; z-index: 1; box-shadow: 0 1px 0 var(--ray-border); }
|
|
37
|
-
[data-align="right"] { text-align: right; }
|
|
37
|
+
[data-align="right"] { text-align: right; } /* physical */
|
|
38
38
|
[data-align="center"] { text-align: center; }
|
|
39
39
|
:host([striped]) tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--ray-muted) 60%, transparent); }
|
|
40
40
|
tbody tr:hover td { background: var(--ray-default-hover); }
|
|
@@ -61,7 +61,7 @@ const styles = css`
|
|
|
61
61
|
th[aria-sort="ascending"] .sort, th[aria-sort="descending"] .sort { color: var(--ray-foreground); }
|
|
62
62
|
th[aria-sort="ascending"] .sort svg, th[aria-sort="descending"] .sort svg { opacity: 1; }
|
|
63
63
|
th[aria-sort="descending"] .sort svg { transform: rotate(180deg); }
|
|
64
|
-
.check { width: 1%; padding-
|
|
64
|
+
.check { width: 1%; padding-inline-end: 0; }
|
|
65
65
|
.check input { width: 1rem; height: 1rem; margin: 0; accent-color: var(--ray-primary); cursor: pointer; vertical-align: middle; }
|
|
66
66
|
.empty td { padding: var(--ray-space-8) var(--_px); text-align: center; color: var(--ray-muted-foreground); }
|
|
67
67
|
.empty:hover td { background: inherit; }
|
package/components/tabs/tabs.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// panel share a `value`.
|
|
6
6
|
|
|
7
7
|
import { RayElement, css, define, uid } from "../../core/core.js";
|
|
8
|
+
import { inlineKey } from "../../core/direction.js";
|
|
8
9
|
import { applyColor } from "../../core/styles.js";
|
|
9
10
|
|
|
10
11
|
const tabsStyles = css`
|
|
@@ -24,9 +25,9 @@ const tabsStyles = css`
|
|
|
24
25
|
}
|
|
25
26
|
.list::-webkit-scrollbar { display: none; }
|
|
26
27
|
.list ::slotted(ray-tab) { flex: none; }
|
|
27
|
-
:host([orientation="vertical"]) .list { flex-direction: column; overflow-x: visible; box-shadow: none; border-
|
|
28
|
+
:host([orientation="vertical"]) .list { flex-direction: column; overflow-x: visible; box-shadow: none; border-inline-end: 2px solid var(--ray-border); }
|
|
28
29
|
:host([variant="outline"]) .list { --_line: 1px; }
|
|
29
|
-
:host([variant="outline"][orientation="vertical"]) .list { border-
|
|
30
|
+
:host([variant="outline"][orientation="vertical"]) .list { border-inline-end-width: 1px; }
|
|
30
31
|
:host([variant="pills"]) .list { gap: var(--ray-space-1); border: 0; box-shadow: none; }
|
|
31
32
|
:host([grow]) .list ::slotted(ray-tab) { flex: 1 0 auto; }
|
|
32
33
|
.panels { flex: 1; min-width: 0; padding-top: var(--ray-space-3); }
|
|
@@ -114,9 +115,11 @@ export class RayTabs extends RayElement {
|
|
|
114
115
|
const vertical = this.orientation === "vertical";
|
|
115
116
|
const next = vertical ? "ArrowDown" : "ArrowRight";
|
|
116
117
|
const prev = vertical ? "ArrowUp" : "ArrowLeft";
|
|
118
|
+
// Right to left, ArrowLeft is the next tab.
|
|
119
|
+
const key = vertical ? e.key : inlineKey(e.key, this);
|
|
117
120
|
let target = null;
|
|
118
|
-
if (
|
|
119
|
-
else if (
|
|
121
|
+
if (key === next) target = tabs[(i + 1) % tabs.length];
|
|
122
|
+
else if (key === prev) target = tabs[(i - 1 + tabs.length) % tabs.length];
|
|
120
123
|
else if (e.key === "Home") target = tabs[0];
|
|
121
124
|
else if (e.key === "End") target = tabs[tabs.length - 1];
|
|
122
125
|
else if ((e.key === "Enter" || e.key === " ") && this.activation === "manual") {
|
|
@@ -157,14 +160,14 @@ const tabStyles = css`
|
|
|
157
160
|
:host([data-variant="default"]) { border-bottom: 2px solid transparent; }
|
|
158
161
|
:host([data-variant="default"]:hover:not([data-active])) { border-bottom-color: var(--ray-border); background: var(--ray-muted); }
|
|
159
162
|
:host([data-variant="default"][data-active]) { border-bottom-color: var(--_filled, var(--ray-primary)); }
|
|
160
|
-
:host([data-variant="default"][data-vertical]) { margin: 0 -2px
|
|
161
|
-
:host([data-variant="default"][data-vertical][data-active]) { border-
|
|
163
|
+
:host([data-variant="default"][data-vertical]) { margin: 0; margin-inline-end: -2px; justify-content: flex-start; border-bottom: 0; border-inline-end: 2px solid transparent; }
|
|
164
|
+
:host([data-variant="default"][data-vertical][data-active]) { border-inline-end-color: var(--_filled, var(--ray-primary)); }
|
|
162
165
|
|
|
163
166
|
/* outline */
|
|
164
167
|
:host([data-variant="outline"]) { border: 1px solid transparent; border-radius: var(--ray-radius-default) var(--ray-radius-default) 0 0; }
|
|
165
168
|
:host([data-variant="outline"][data-active]) { border-color: var(--ray-border); border-bottom-color: var(--ray-background); background: var(--ray-background); }
|
|
166
|
-
:host([data-variant="outline"][data-vertical]) { margin: 0 -1px
|
|
167
|
-
:host([data-variant="outline"][data-vertical][data-active]) { border-bottom-color: var(--ray-border); border-
|
|
169
|
+
:host([data-variant="outline"][data-vertical]) { margin: 0; margin-inline-end: -1px; justify-content: flex-start; border-radius: 0; border-start-start-radius: var(--ray-radius-default); border-end-start-radius: var(--ray-radius-default); }
|
|
170
|
+
:host([data-variant="outline"][data-vertical][data-active]) { border-bottom-color: var(--ray-border); border-inline-end-color: var(--ray-background); }
|
|
168
171
|
|
|
169
172
|
/* pills */
|
|
170
173
|
:host([data-variant="pills"]) { border-radius: var(--ray-radius-default); }
|
|
@@ -22,9 +22,14 @@ const itemStyles = css`
|
|
|
22
22
|
}
|
|
23
23
|
:host([hidden]) { display: none; }
|
|
24
24
|
:host([data-last]) { padding-bottom: 0; }
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
:host([data-align="
|
|
25
|
+
/* The line at the end: end, or left/right as sides of the screen (the
|
|
26
|
+
grid's columns follow the writing direction). */
|
|
27
|
+
:host([data-align="end"]), :host([data-align="right"]:dir(ltr)), :host([data-align="left"]:dir(rtl)) {
|
|
28
|
+
grid-template-columns: minmax(0, 1fr) var(--_bullet);
|
|
29
|
+
text-align: end;
|
|
30
|
+
}
|
|
31
|
+
:host([data-align="end"]) .bullet-col, :host([data-align="right"]:dir(ltr)) .bullet-col, :host([data-align="left"]:dir(rtl)) .bullet-col { grid-column: 2; grid-row: 1; }
|
|
32
|
+
:host([data-align="end"]) .body, :host([data-align="right"]:dir(ltr)) .body, :host([data-align="left"]:dir(rtl)) .body { grid-column: 1; grid-row: 1; }
|
|
28
33
|
.bullet-col { position: relative; display: flex; justify-content: center; }
|
|
29
34
|
.bullet {
|
|
30
35
|
position: relative;
|
|
@@ -47,12 +52,12 @@ const itemStyles = css`
|
|
|
47
52
|
position: absolute;
|
|
48
53
|
top: var(--_bullet);
|
|
49
54
|
bottom: calc(-1 * var(--ray-space-6));
|
|
50
|
-
left: calc(50% - var(--_line) / 2);
|
|
55
|
+
left: calc(50% - var(--_line) / 2); /* physical */
|
|
51
56
|
width: 0;
|
|
52
|
-
border-left: var(--_line) var(--_line-style, solid) var(--ray-border);
|
|
57
|
+
border-left: var(--_line) var(--_line-style, solid) var(--ray-border); /* physical */
|
|
53
58
|
}
|
|
54
59
|
:host([data-last]) .line { display: none; }
|
|
55
|
-
:host([data-line-reached]) .line { border-left-color: var(--_filled); }
|
|
60
|
+
:host([data-line-reached]) .line { border-left-color: var(--_filled); } /* physical */
|
|
56
61
|
:host([line-variant="dashed"]) .line { --_line-style: dashed; }
|
|
57
62
|
:host([line-variant="dotted"]) .line { --_line-style: dotted; }
|
|
58
63
|
.body { min-width: 0; padding-top: calc((var(--_bullet) - 1.4em) / 2); }
|
|
@@ -67,7 +72,7 @@ const itemStyles = css`
|
|
|
67
72
|
export class RayTimeline extends RayElement {
|
|
68
73
|
static properties = {
|
|
69
74
|
active: { type: Number, reflect: true, default: -1 },
|
|
70
|
-
align: { type: String, reflect: true, default: "
|
|
75
|
+
align: { type: String, reflect: true, default: "start" },
|
|
71
76
|
bulletSize: { type: String, attribute: "bullet-size" },
|
|
72
77
|
lineWidth: { type: Number, attribute: "line-width" },
|
|
73
78
|
color: { type: String, reflect: true },
|
|
@@ -106,7 +111,7 @@ export class RayTimeline extends RayElement {
|
|
|
106
111
|
item.toggleAttribute("data-reached", active >= 0 && k <= active);
|
|
107
112
|
item.toggleAttribute("data-line-reached", active >= 0 && (this.reverseActive ? k <= active && k > 0 : k < active));
|
|
108
113
|
item.toggleAttribute("data-last", i === n - 1);
|
|
109
|
-
item.setAttribute("data-align",
|
|
114
|
+
item.setAttribute("data-align", ["end", "left", "right"].includes(this.align) ? this.align : "start");
|
|
110
115
|
});
|
|
111
116
|
}
|
|
112
117
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// </ray-toggle-group>
|
|
14
14
|
|
|
15
15
|
import { css, define } from "../../core/core.js";
|
|
16
|
+
import { inlineKey } from "../../core/direction.js";
|
|
16
17
|
import { RayButtonGroup } from "../button-group/button-group.js";
|
|
17
18
|
import "../toggle/toggle.js";
|
|
18
19
|
|
|
@@ -113,14 +114,15 @@ export class RayToggleGroup extends RayButtonGroup {
|
|
|
113
114
|
const vertical = this.orientation === "vertical";
|
|
114
115
|
const next = vertical ? "ArrowDown" : "ArrowRight";
|
|
115
116
|
const prev = vertical ? "ArrowUp" : "ArrowLeft";
|
|
116
|
-
|
|
117
|
+
const key = vertical ? e.key : inlineKey(e.key, this);
|
|
118
|
+
if (![next, prev, "Home", "End"].includes(key)) return;
|
|
117
119
|
const items = this.toggles.filter((t) => !t.disabled);
|
|
118
120
|
if (items.length === 0) return;
|
|
119
121
|
const current = items.findIndex((t) => t.matches(":focus-within"));
|
|
120
122
|
let i = current;
|
|
121
|
-
if (
|
|
122
|
-
else if (
|
|
123
|
-
else if (
|
|
123
|
+
if (key === "Home") i = 0;
|
|
124
|
+
else if (key === "End") i = items.length - 1;
|
|
125
|
+
else if (key === next) i = (current + 1) % items.length;
|
|
124
126
|
else i = (current - 1 + items.length) % items.length;
|
|
125
127
|
e.preventDefault();
|
|
126
128
|
this.#rove(items[i]);
|
package/core/core.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// the theme API. No dependencies; loads as a plain ES module (also from
|
|
3
3
|
// ray://app in a std/ui window).
|
|
4
4
|
|
|
5
|
-
import { getIcon, onIconsChange } from "./icons.js";
|
|
5
|
+
import { getIcon, mirrorsInRtl, onIconsChange } from "./icons.js";
|
|
6
6
|
import { parseColor, shades, contrastRatio, toHex, toOklch } from "./color.js";
|
|
7
7
|
|
|
8
8
|
export { setIcons, getIcon, iconNames, iconAliases } from "./icons.js";
|
|
@@ -59,6 +59,8 @@ export const hostStyles = css`
|
|
|
59
59
|
height: var(--ray-icon-size, 1em);
|
|
60
60
|
stroke-width: var(--ray-icon-stroke, 2);
|
|
61
61
|
}
|
|
62
|
+
/* Right to left, the roles that point along the text. */
|
|
63
|
+
:where([data-ray-icon-mirror]):dir(rtl) > svg { transform: scaleX(-1); }
|
|
62
64
|
.focus-ring:focus-visible {
|
|
63
65
|
outline: var(--ray-focus-ring-width) solid var(--ray-ring);
|
|
64
66
|
outline-offset: var(--ray-focus-ring-offset);
|
|
@@ -315,11 +317,14 @@ export class RayElement extends HTMLElement {
|
|
|
315
317
|
* it for markup they create after the template. An empty name or "none"
|
|
316
318
|
* (the value components use to hide an icon) leaves the slot empty;
|
|
317
319
|
* data-ray-icon-filled asks for the filled version (the outline without one).
|
|
320
|
+
* A role that points along the text (ui.next…) gets data-ray-icon-mirror,
|
|
321
|
+
* which mirrors it under dir="rtl".
|
|
318
322
|
*/
|
|
319
323
|
paintIcons(root = this.shadowRoot) {
|
|
320
324
|
for (const slot of root.querySelectorAll("[data-ray-icon]")) {
|
|
321
325
|
const name = slot.getAttribute("data-ray-icon");
|
|
322
326
|
slot.innerHTML = name && name !== "none" ? getIcon(name, { filled: slot.hasAttribute("data-ray-icon-filled") }) : "";
|
|
327
|
+
slot.toggleAttribute("data-ray-icon-mirror", mirrorsInRtl(name));
|
|
323
328
|
}
|
|
324
329
|
}
|
|
325
330
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// The writing direction, for what CSS does not mirror by itself: arrow keys
|
|
2
|
+
// and pointer or scroll coordinates. Read when used (not cached), so a
|
|
3
|
+
// change of dir takes effect at once.
|
|
4
|
+
|
|
5
|
+
/** Whether `el` lays out right to left (dir="rtl" on it or an ancestor). */
|
|
6
|
+
export function isRtl(el) {
|
|
7
|
+
return getComputedStyle(el).direction === "rtl";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An arrow key as the reading direction sees it. Right to left, ArrowLeft
|
|
12
|
+
* moves forward, so it comes back as "ArrowRight", and ArrowRight as
|
|
13
|
+
* "ArrowLeft"; any other key as it is.
|
|
14
|
+
* if (inlineKey(e.key, this) === "ArrowRight") this.next();
|
|
15
|
+
*/
|
|
16
|
+
export function inlineKey(key, el) {
|
|
17
|
+
if ((key === "ArrowLeft" || key === "ArrowRight") && isRtl(el)) return key === "ArrowLeft" ? "ArrowRight" : "ArrowLeft";
|
|
18
|
+
return key;
|
|
19
|
+
}
|