@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
package/base.css
CHANGED
|
@@ -17,7 +17,7 @@ export class RayAccordion extends RayElement {
|
|
|
17
17
|
value: { type: String },
|
|
18
18
|
multiple: { type: Boolean, reflect: true },
|
|
19
19
|
variant: { type: String, reflect: true, default: "default" },
|
|
20
|
-
chevronPosition: { type: String, reflect: true, default: "
|
|
20
|
+
chevronPosition: { type: String, reflect: true, default: "end" },
|
|
21
21
|
};
|
|
22
22
|
static styles = [accordionStyles];
|
|
23
23
|
static template = `<div class="items" part="items"><slot></slot></div>`;
|
|
@@ -52,7 +52,7 @@ export class RayAccordion extends RayElement {
|
|
|
52
52
|
items.forEach((item, i) => {
|
|
53
53
|
item._setOpen(open.includes(item.value));
|
|
54
54
|
item.dataset.variant = this.variant || "default";
|
|
55
|
-
item.dataset.chevron = this.chevronPosition || "
|
|
55
|
+
item.dataset.chevron = this.chevronPosition || "end";
|
|
56
56
|
item.toggleAttribute("data-first", i === 0);
|
|
57
57
|
item.toggleAttribute("data-last", i === items.length - 1);
|
|
58
58
|
});
|
|
@@ -104,7 +104,7 @@ const itemStyles = css`
|
|
|
104
104
|
font-size: var(--ray-font-size-md);
|
|
105
105
|
font-weight: var(--ray-font-weight-medium);
|
|
106
106
|
color: var(--ray-foreground);
|
|
107
|
-
text-align:
|
|
107
|
+
text-align: start;
|
|
108
108
|
background: transparent;
|
|
109
109
|
border: 0;
|
|
110
110
|
border-radius: inherit;
|
|
@@ -116,7 +116,10 @@ const itemStyles = css`
|
|
|
116
116
|
.label { display: grid; flex: 1; gap: 0.125rem; min-width: 0; }
|
|
117
117
|
.description { font-size: var(--ray-font-size-sm); font-weight: var(--ray-font-weight-normal, 400); color: var(--ray-muted-foreground); }
|
|
118
118
|
.description:empty { display: none; }
|
|
119
|
-
|
|
119
|
+
/* The chevron before the label: start, or left/right as sides of the screen. */
|
|
120
|
+
:host([data-chevron="start"]) .chevron,
|
|
121
|
+
:host([data-chevron="left"]) .chevron:dir(ltr),
|
|
122
|
+
:host([data-chevron="right"]) .chevron:dir(rtl) { order: -1; }
|
|
120
123
|
.chevron { display: inline-flex; color: var(--ray-muted-foreground); transition: transform var(--ray-duration-base) var(--ray-easing-standard); }
|
|
121
124
|
.chevron svg { width: 1rem; height: 1rem; }
|
|
122
125
|
:host([data-open]) .chevron { transform: rotate(180deg); }
|
|
@@ -25,7 +25,7 @@ const styles = css`
|
|
|
25
25
|
width: 16rem;
|
|
26
26
|
max-width: 100%;
|
|
27
27
|
padding: var(--ray-space-2);
|
|
28
|
-
padding-
|
|
28
|
+
padding-inline-end: var(--ray-space-3);
|
|
29
29
|
color: var(--ray-foreground);
|
|
30
30
|
background: var(--ray-card);
|
|
31
31
|
border: 1px solid var(--ray-border);
|
|
@@ -53,7 +53,7 @@ const styles = css`
|
|
|
53
53
|
.badge {
|
|
54
54
|
position: absolute;
|
|
55
55
|
bottom: 3px;
|
|
56
|
-
left: 50%;
|
|
56
|
+
left: 50%; /* physical */
|
|
57
57
|
transform: translateX(-50%);
|
|
58
58
|
padding: 0 0.25rem;
|
|
59
59
|
font-size: 0.5625rem;
|
|
@@ -82,8 +82,7 @@ const styles = css`
|
|
|
82
82
|
:host([status="error"]) .meta { color: var(--ray-destructive); }
|
|
83
83
|
.bar {
|
|
84
84
|
position: absolute;
|
|
85
|
-
|
|
86
|
-
right: 0;
|
|
85
|
+
inset-inline: 0;
|
|
87
86
|
bottom: 0;
|
|
88
87
|
height: 2px;
|
|
89
88
|
background: var(--ray-muted);
|
|
@@ -131,7 +130,7 @@ const styles = css`
|
|
|
131
130
|
:host([variant="card"]) .remove {
|
|
132
131
|
position: absolute;
|
|
133
132
|
top: calc(var(--ray-space-2) + 4px);
|
|
134
|
-
|
|
133
|
+
inset-inline-end: calc(var(--ray-space-2) + 4px);
|
|
135
134
|
background: var(--ray-card);
|
|
136
135
|
box-shadow: var(--ray-shadow-xs);
|
|
137
136
|
opacity: 1;
|
|
@@ -22,7 +22,7 @@ const styles = css`
|
|
|
22
22
|
:host([hide-edge]) { --_edge: 0px; }
|
|
23
23
|
figure { position: relative; margin: 0; }
|
|
24
24
|
/* Room for the mark, so it does not cover what is above or beside. */
|
|
25
|
-
:host([data-icon]) figure { padding: calc(var(--_icon) / 2)
|
|
25
|
+
:host([data-icon]) figure { padding-block-start: calc(var(--_icon) / 2); padding-inline-start: calc(var(--_icon) / 2); }
|
|
26
26
|
blockquote {
|
|
27
27
|
margin: 0;
|
|
28
28
|
padding: var(--_p) calc(var(--_p) * 1.25);
|
|
@@ -43,7 +43,7 @@ const styles = css`
|
|
|
43
43
|
.icon {
|
|
44
44
|
position: absolute;
|
|
45
45
|
top: 0;
|
|
46
|
-
|
|
46
|
+
inset-inline-start: 0;
|
|
47
47
|
display: grid;
|
|
48
48
|
place-items: center;
|
|
49
49
|
width: var(--_icon);
|
|
@@ -40,7 +40,7 @@ const navStyles = css`
|
|
|
40
40
|
grid-auto-columns: minmax(0, 1fr);
|
|
41
41
|
grid-auto-flow: column;
|
|
42
42
|
align-items: stretch;
|
|
43
|
-
padding: var(--ray-space-1) max(var(--ray-space-1), env(safe-area-inset-right, 0px)) max(var(--ray-space-1), env(safe-area-inset-bottom, 0px)) max(var(--ray-space-1), env(safe-area-inset-left, 0px));
|
|
43
|
+
padding: var(--ray-space-1) max(var(--ray-space-1), env(safe-area-inset-right, 0px)) max(var(--ray-space-1), env(safe-area-inset-bottom, 0px)) max(var(--ray-space-1), env(safe-area-inset-left, 0px)); /* physical */
|
|
44
44
|
background: var(--ray-bottom-nav, var(--ray-background));
|
|
45
45
|
border-top: 1px solid var(--ray-border);
|
|
46
46
|
}
|
|
@@ -109,7 +109,7 @@ const itemStyles = css`
|
|
|
109
109
|
.badge {
|
|
110
110
|
position: absolute;
|
|
111
111
|
top: 0;
|
|
112
|
-
|
|
112
|
+
inset-inline-start: calc(50% + 0.375rem);
|
|
113
113
|
min-width: 1rem;
|
|
114
114
|
height: 1rem;
|
|
115
115
|
padding: 0 0.25rem;
|
|
@@ -125,7 +125,7 @@ const itemStyles = css`
|
|
|
125
125
|
box-sizing: content-box;
|
|
126
126
|
}
|
|
127
127
|
.badge:empty:not([data-dot]) { display: none; }
|
|
128
|
-
.badge[data-dot] { min-width: 0; width: 0.5rem; height: 0.5rem; padding: 0; top: 0.25rem;
|
|
128
|
+
.badge[data-dot] { min-width: 0; width: 0.5rem; height: 0.5rem; padding: 0; top: 0.25rem; inset-inline-start: calc(50% + 0.5rem); }
|
|
129
129
|
.label {
|
|
130
130
|
max-width: 100%;
|
|
131
131
|
overflow: hidden;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { RayElement, css, define } from "../../core/core.js";
|
|
11
11
|
import { motionAllowed } from "../../core/motion.js";
|
|
12
|
+
import { isRtl, inlineKey } from "../../core/direction.js";
|
|
12
13
|
|
|
13
14
|
const styles = css`
|
|
14
15
|
:host {
|
|
@@ -74,9 +75,11 @@ const styles = css`
|
|
|
74
75
|
.control:hover:not(:disabled) { background: var(--ray-accent); }
|
|
75
76
|
.control:disabled { opacity: 0; pointer-events: none; }
|
|
76
77
|
.control svg { width: 1rem; height: 1rem; }
|
|
77
|
-
.prev {
|
|
78
|
-
.next {
|
|
79
|
-
:host([orientation="vertical"]) .control { top: auto; left: 50%; transform: translateX(-50%) rotate(90deg); }
|
|
78
|
+
.prev { inset-inline-start: var(--ray-space-2); }
|
|
79
|
+
.next { inset-inline-end: var(--ray-space-2); }
|
|
80
|
+
:host([orientation="vertical"]) .control { top: auto; left: 50%; transform: translateX(-50%) rotate(90deg); } /* physical */
|
|
81
|
+
/* Up and down do not depend on the writing direction: the turned arrows are not mirrored. */
|
|
82
|
+
:host([orientation="vertical"]) .control [data-ray-icon] > svg { transform: none; }
|
|
80
83
|
:host([orientation="vertical"]) .prev { top: var(--ray-space-2); }
|
|
81
84
|
:host([orientation="vertical"]) .next { bottom: var(--ray-space-2); }
|
|
82
85
|
:host([hide-controls]) .control { display: none; }
|
|
@@ -272,6 +275,14 @@ export class RayCarousel extends RayElement {
|
|
|
272
275
|
return this.orientation === "vertical";
|
|
273
276
|
}
|
|
274
277
|
|
|
278
|
+
// The scroll offset from the start. Right to left, scrollLeft runs from 0
|
|
279
|
+
// (the start, on the right) down to negative values.
|
|
280
|
+
_scroll() {
|
|
281
|
+
const vp = this.viewport;
|
|
282
|
+
if (this._vertical()) return vp.scrollTop;
|
|
283
|
+
return isRtl(this) ? -vp.scrollLeft : vp.scrollLeft;
|
|
284
|
+
}
|
|
285
|
+
|
|
275
286
|
_current() {
|
|
276
287
|
return Math.min(Math.max(0, this.index ?? 0), this.count - 1);
|
|
277
288
|
}
|
|
@@ -294,11 +305,12 @@ export class RayCarousel extends RayElement {
|
|
|
294
305
|
const vr = vp.getBoundingClientRect();
|
|
295
306
|
const size = v ? vp.clientHeight : vp.clientWidth;
|
|
296
307
|
const max = Math.max(0, v ? vp.scrollHeight - vp.clientHeight : vp.scrollWidth - vp.clientWidth);
|
|
297
|
-
const scroll =
|
|
308
|
+
const scroll = this._scroll();
|
|
309
|
+
const rtl = !v && isRtl(this);
|
|
298
310
|
const snaps = [];
|
|
299
311
|
for (const s of slides) {
|
|
300
312
|
const r = s.getBoundingClientRect();
|
|
301
|
-
const start = (v ? r.top - vr.top : r.left - vr.left) + scroll;
|
|
313
|
+
const start = (v ? r.top - vr.top : rtl ? vr.right - r.right : r.left - vr.left) + scroll;
|
|
302
314
|
const length = v ? r.height : r.width;
|
|
303
315
|
const pos = this.align === "center" ? start - (size - length) / 2 : start;
|
|
304
316
|
const clamped = Math.round(Math.min(max, Math.max(0, pos)));
|
|
@@ -333,8 +345,7 @@ export class RayCarousel extends RayElement {
|
|
|
333
345
|
|
|
334
346
|
// The position closest to the current scroll offset.
|
|
335
347
|
_track() {
|
|
336
|
-
const
|
|
337
|
-
const scroll = this._vertical() ? vp.scrollTop : vp.scrollLeft;
|
|
348
|
+
const scroll = this._scroll();
|
|
338
349
|
let best = 0;
|
|
339
350
|
for (let i = 1; i < this.#snaps.length; i++) {
|
|
340
351
|
if (Math.abs(this.#snaps[i] - scroll) < Math.abs(this.#snaps[best] - scroll)) best = i;
|
|
@@ -358,7 +369,7 @@ export class RayCarousel extends RayElement {
|
|
|
358
369
|
const smooth = user && !instant && !reduced?.matches && motionAllowed(this);
|
|
359
370
|
const opts = { behavior: smooth ? "smooth" : "instant" };
|
|
360
371
|
if (this._vertical()) this.viewport.scrollTo({ top: pos, ...opts });
|
|
361
|
-
else this.viewport.scrollTo({ left: pos, ...opts });
|
|
372
|
+
else this.viewport.scrollTo({ left: isRtl(this) ? -pos : pos, ...opts });
|
|
362
373
|
this._setIndex(i);
|
|
363
374
|
this._paint();
|
|
364
375
|
this._schedule();
|
|
@@ -416,10 +427,11 @@ export class RayCarousel extends RayElement {
|
|
|
416
427
|
const v = this._vertical();
|
|
417
428
|
const back = v ? "ArrowUp" : "ArrowLeft";
|
|
418
429
|
const fwd = v ? "ArrowDown" : "ArrowRight";
|
|
419
|
-
|
|
430
|
+
const key = v ? e.key : inlineKey(e.key, this);
|
|
431
|
+
if (key === fwd) {
|
|
420
432
|
e.preventDefault();
|
|
421
433
|
this.next();
|
|
422
|
-
} else if (
|
|
434
|
+
} else if (key === back) {
|
|
423
435
|
e.preventDefault();
|
|
424
436
|
this.prev();
|
|
425
437
|
} else if (e.key === "Home") {
|
|
@@ -19,7 +19,10 @@ export const checkStyles = css`
|
|
|
19
19
|
align-items: flex-start;
|
|
20
20
|
gap: calc(var(--_s) * 0.6);
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
/* The label before the box: start, or left/right as sides of the screen. */
|
|
23
|
+
:host([label-position="start"]) .root,
|
|
24
|
+
:host([label-position="left"]) .root:dir(ltr),
|
|
25
|
+
:host([label-position="right"]) .root:dir(rtl) { flex-direction: row-reverse; justify-content: flex-end; }
|
|
23
26
|
.inner {
|
|
24
27
|
position: relative;
|
|
25
28
|
flex: none;
|
|
@@ -100,7 +103,7 @@ export class RayCheckbox extends FieldControl {
|
|
|
100
103
|
value: { type: String, reflect: true, default: "on" },
|
|
101
104
|
color: { type: String, reflect: true },
|
|
102
105
|
variant: { type: String, reflect: true, default: "filled" },
|
|
103
|
-
labelPosition: { type: String, reflect: true, default: "
|
|
106
|
+
labelPosition: { type: String, reflect: true, default: "end" },
|
|
104
107
|
};
|
|
105
108
|
static styles = [sizeStyles, radiusStyles, fieldStyles, checkStyles, styles];
|
|
106
109
|
static template = checkTemplate(`<span class="indicator" part="indicator" aria-hidden="true"><span class="check" data-ray-icon="ui.checked"></span><span class="dash" data-ray-icon="ui.indeterminate"></span></span>`);
|
|
@@ -15,7 +15,7 @@ const styles = css`
|
|
|
15
15
|
.items { display: flex; flex-direction: column; gap: var(--ray-space-3); margin-top: var(--ray-space-2); }
|
|
16
16
|
:host([orientation="horizontal"]) .items { flex-direction: row; flex-wrap: wrap; gap: var(--ray-space-3) var(--ray-space-5); }
|
|
17
17
|
.label { margin-bottom: 0; }
|
|
18
|
-
.count { margin-
|
|
18
|
+
.count { margin-inline-start: var(--ray-space-1); font-weight: 400; color: var(--ray-muted-foreground); font-variant-numeric: tabular-nums; }
|
|
19
19
|
`;
|
|
20
20
|
|
|
21
21
|
/** "a, b" (an attribute) or an array, as an array of values. */
|
package/components/code/code.js
CHANGED
|
@@ -293,7 +293,8 @@ const styles = css`
|
|
|
293
293
|
align-items: center;
|
|
294
294
|
gap: var(--ray-space-2);
|
|
295
295
|
min-height: 2.5rem;
|
|
296
|
-
padding: 0
|
|
296
|
+
padding-block: 0;
|
|
297
|
+
padding-inline: var(--ray-space-4) var(--ray-space-2);
|
|
297
298
|
font-size: var(--ray-font-size-xs);
|
|
298
299
|
color: var(--ray-muted-foreground);
|
|
299
300
|
border-bottom: 1px solid var(--ray-border);
|
|
@@ -325,12 +326,13 @@ const styles = css`
|
|
|
325
326
|
.copy-label { display: none; }
|
|
326
327
|
.copy[data-copied] .copy-label { display: inline; }
|
|
327
328
|
/* Without a header, the button floats in the top-end corner. */
|
|
328
|
-
.copy.floating { position: absolute; z-index: 1; top: var(--ray-space-2);
|
|
329
|
+
.copy.floating { position: absolute; z-index: 1; top: var(--ray-space-2); inset-inline-end: var(--ray-space-2); background: var(--ray-code-background); opacity: 0; transition: opacity var(--ray-duration-fast); }
|
|
329
330
|
.frame:hover .copy.floating, .copy.floating:focus-visible, .copy.floating[data-copied] { opacity: 1; }
|
|
330
331
|
@media (hover: none) { .copy.floating { opacity: 1; } }
|
|
331
332
|
.copy[hidden] { display: none; }
|
|
332
333
|
|
|
333
|
-
|
|
334
|
+
/* Code reads left to right, in a right-to-left page too. */
|
|
335
|
+
.scroller { direction: ltr; overflow: auto; max-height: var(--_max, none); outline: none; }
|
|
334
336
|
.scroller:focus-visible { box-shadow: inset 0 0 0 2px var(--ray-ring); }
|
|
335
337
|
:host([expanded]) .scroller { max-height: none; }
|
|
336
338
|
pre { margin: 0; padding: var(--ray-space-3) 0; font-family: var(--ray-font-mono); font-size: var(--_fs); line-height: var(--_lh); tab-size: 2; }
|
|
@@ -339,19 +341,19 @@ const styles = css`
|
|
|
339
341
|
.line { display: block; padding: 0 var(--ray-space-4); white-space: pre; border-inline-start: 2px solid transparent; }
|
|
340
342
|
:host([wrap]) .line { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
341
343
|
.line:empty::after { content: " "; }
|
|
342
|
-
:host([line-numbers]) .line { counter-increment: line; padding-
|
|
344
|
+
:host([line-numbers]) .line { counter-increment: line; padding-inline-start: 0; }
|
|
343
345
|
:host([line-numbers]) .line::before {
|
|
344
346
|
content: counter(line);
|
|
345
347
|
display: inline-block;
|
|
346
348
|
width: calc(var(--_digits, 2) * 1ch + var(--ray-space-6));
|
|
347
349
|
padding-inline-end: var(--ray-space-4);
|
|
348
350
|
box-sizing: border-box;
|
|
349
|
-
text-align:
|
|
351
|
+
text-align: end;
|
|
350
352
|
color: var(--ray-code-comment);
|
|
351
353
|
opacity: 0.7;
|
|
352
354
|
user-select: none;
|
|
353
355
|
}
|
|
354
|
-
:host([line-numbers][wrap]) .line { padding-
|
|
356
|
+
:host([line-numbers][wrap]) .line { padding-inline-start: calc(var(--_digits, 2) * 1ch + var(--ray-space-6)); text-indent: calc((var(--_digits, 2) * 1ch + var(--ray-space-6)) * -1); }
|
|
355
357
|
.line[data-highlight] { background: color-mix(in srgb, var(--ray-primary) 12%, transparent); border-inline-start-color: var(--ray-primary); }
|
|
356
358
|
.line[data-diff="add"] { color: var(--ray-code-added); background: color-mix(in srgb, var(--ray-code-added) 12%, transparent); }
|
|
357
359
|
.line[data-diff="remove"] { color: var(--ray-code-removed); background: color-mix(in srgb, var(--ray-code-removed) 12%, transparent); }
|
|
@@ -172,7 +172,7 @@ const pickerStyles = css`
|
|
|
172
172
|
height: calc(var(--_w) * 0.62);
|
|
173
173
|
background:
|
|
174
174
|
linear-gradient(to top, #000, transparent),
|
|
175
|
-
linear-gradient(to right, #fff, hsl(var(--_hue) 100% 50%));
|
|
175
|
+
linear-gradient(to right, #fff, hsl(var(--_hue) 100% 50%)); /* physical */
|
|
176
176
|
border-radius: var(--ray-radius-sm);
|
|
177
177
|
cursor: crosshair;
|
|
178
178
|
touch-action: none;
|
|
@@ -180,7 +180,7 @@ const pickerStyles = css`
|
|
|
180
180
|
:host([full-width]) .area { height: auto; aspect-ratio: 5 / 3; }
|
|
181
181
|
.area-thumb {
|
|
182
182
|
position: absolute;
|
|
183
|
-
left: var(--_s);
|
|
183
|
+
left: var(--_s); /* physical */
|
|
184
184
|
top: calc(100% - var(--_v));
|
|
185
185
|
width: var(--_thumb);
|
|
186
186
|
height: var(--_thumb);
|
|
@@ -209,13 +209,13 @@ const pickerStyles = css`
|
|
|
209
209
|
inset: 0 calc(var(--_thumb) / -2);
|
|
210
210
|
border-radius: inherit;
|
|
211
211
|
}
|
|
212
|
-
.hue::before { background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
|
|
213
|
-
.alpha::before { background: linear-gradient(to right, transparent, var(--_solid)), ${CHECKER}; }
|
|
212
|
+
.hue::before { background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); } /* physical */
|
|
213
|
+
.alpha::before { background: linear-gradient(to right, transparent, var(--_solid)), ${CHECKER}; } /* physical */
|
|
214
214
|
:host(:not([data-alpha])) .alpha { display: none; }
|
|
215
215
|
.slider-thumb {
|
|
216
216
|
position: absolute;
|
|
217
217
|
top: 50%;
|
|
218
|
-
left: var(--_pos);
|
|
218
|
+
left: var(--_pos); /* physical */
|
|
219
219
|
width: var(--_thumb);
|
|
220
220
|
height: var(--_thumb);
|
|
221
221
|
box-sizing: border-box;
|
|
@@ -30,7 +30,8 @@ const styles = css`
|
|
|
30
30
|
max-width: 100%;
|
|
31
31
|
height: calc(var(--_h) - 0.75rem);
|
|
32
32
|
min-height: 1.375rem;
|
|
33
|
-
padding: 0
|
|
33
|
+
padding-block: 0;
|
|
34
|
+
padding-inline: 0.5rem 0.125rem;
|
|
34
35
|
font-size: calc(var(--_fs) - 0.0625rem);
|
|
35
36
|
color: var(--ray-foreground);
|
|
36
37
|
background: var(--ray-muted);
|
|
@@ -110,7 +110,7 @@ const styles = css`
|
|
|
110
110
|
.hint b {
|
|
111
111
|
display: inline-block;
|
|
112
112
|
min-width: 1.25rem;
|
|
113
|
-
margin-
|
|
113
|
+
margin-inline-end: var(--ray-space-1);
|
|
114
114
|
padding: 0 0.3rem;
|
|
115
115
|
font-family: var(--ray-font-mono);
|
|
116
116
|
font-size: 0.6875rem;
|
|
@@ -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";
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -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 },
|
package/components/icon/icon.js
CHANGED
|
@@ -1,7 +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
3
|
// `filled` draws the icon's filled version (a state: active, selected,
|
|
4
|
-
// favorite), or the outline one when it has none.
|
|
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.
|
|
5
6
|
|
|
6
7
|
import { RayElement, css, define } from "../../core/core.js";
|
|
7
8
|
|
|
@@ -13,6 +14,7 @@ const styles = css`
|
|
|
13
14
|
size) must not reach the icon. size= overrides it inline. */
|
|
14
15
|
:host { --_size: 1em; display: inline-flex; vertical-align: -0.125em; line-height: 1; }
|
|
15
16
|
.icon svg { width: var(--_size); height: var(--_size); }
|
|
17
|
+
:host([mirror]) .icon:dir(rtl) > svg { transform: scaleX(-1); }
|
|
16
18
|
`;
|
|
17
19
|
|
|
18
20
|
export class RayIcon extends RayElement {
|
|
@@ -23,6 +25,7 @@ export class RayIcon extends RayElement {
|
|
|
23
25
|
color: { type: String, reflect: true },
|
|
24
26
|
stroke: { type: Number },
|
|
25
27
|
filled: { type: Boolean, reflect: true },
|
|
28
|
+
mirror: { type: Boolean, reflect: true },
|
|
26
29
|
};
|
|
27
30
|
static styles = [styles];
|
|
28
31
|
static template = `<span class="icon" part="icon" data-ray-icon=""></span>`;
|
|
@@ -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,7 +60,7 @@ 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 = `
|
|
@@ -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;
|
|
@@ -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}%)`;
|