@raylang/elements 0.7.0 → 0.9.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.
Files changed (58) hide show
  1. package/base.css +1 -1
  2. package/components/accordion/accordion.js +7 -4
  3. package/components/alert/alert.js +2 -1
  4. package/components/attachment/attachment.js +4 -5
  5. package/components/blockquote/blockquote.js +2 -2
  6. package/components/bottom-nav/bottom-nav.js +3 -3
  7. package/components/carousel/carousel.js +22 -10
  8. package/components/checkbox/checkbox.js +5 -2
  9. package/components/checkbox-group/checkbox-group.js +1 -1
  10. package/components/code/code.js +16 -11
  11. package/components/color-picker/color-picker.js +5 -5
  12. package/components/combobox/combobox.js +2 -1
  13. package/components/command-palette/command-palette.js +17 -2
  14. package/components/date-picker/date-picker.js +5 -3
  15. package/components/dialog/dialog.js +1 -1
  16. package/components/doc-tabs/doc-tabs.js +255 -0
  17. package/components/drawer/drawer.js +27 -9
  18. package/components/icon/icon.js +4 -1
  19. package/components/icon-button/icon-button.js +4 -0
  20. package/components/indicator/indicator.js +2 -1
  21. package/components/input/input.js +13 -1
  22. package/components/menu/menu.js +43 -8
  23. package/components/mode-input/mode-input.js +221 -0
  24. package/components/nav-link/nav-link.js +5 -2
  25. package/components/notification/notification.js +15 -12
  26. package/components/progress/progress.js +2 -2
  27. package/components/radio/radio.js +4 -2
  28. package/components/scroll-area/scroll-area.js +14 -11
  29. package/components/search/search.js +3 -3
  30. package/components/segmented-control/segmented-control.js +5 -3
  31. package/components/select/select.js +3 -3
  32. package/components/separator/separator.js +8 -3
  33. package/components/sidebar/sidebar.js +154 -11
  34. package/components/slider/slider.js +5 -2
  35. package/components/split/split.js +25 -7
  36. package/components/stepper/stepper.js +4 -4
  37. package/components/switch/switch.js +3 -3
  38. package/components/table/table.js +5 -5
  39. package/components/tabs/tabs.js +55 -14
  40. package/components/textarea/textarea.js +13 -1
  41. package/components/timeline/timeline.js +13 -8
  42. package/components/toggle-group/toggle-group.js +6 -4
  43. package/core/complete.js +252 -0
  44. package/core/core.js +6 -1
  45. package/core/direction.js +19 -0
  46. package/core/icons.js +12 -0
  47. package/core/position.js +11 -3
  48. package/core/scroll.js +85 -1
  49. package/core/styles.js +1 -1
  50. package/core/textlayer.js +214 -0
  51. package/core.js +1 -0
  52. package/custom-elements.json +1487 -143
  53. package/index.js +2 -0
  54. package/llms-full.txt +224 -28
  55. package/llms.txt +26 -4
  56. package/package.json +9 -1
  57. package/ray-elements.d.ts +190 -31
  58. package/vscode.html-custom-data.json +477 -22
package/base.css CHANGED
@@ -84,7 +84,7 @@
84
84
  }
85
85
  :where(.ray-table) :where(th, td) {
86
86
  padding: var(--ray-space-2) var(--ray-space-3);
87
- text-align: left;
87
+ text-align: start;
88
88
  vertical-align: middle;
89
89
  border-bottom: 1px solid var(--ray-border);
90
90
  }
@@ -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: "right" },
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 || "right";
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: left;
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
- :host([data-chevron="left"]) .chevron { order: -1; }
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); }
@@ -34,7 +34,8 @@ const styles = css`
34
34
  justify-content: center;
35
35
  width: 1.5rem;
36
36
  height: 1.5rem;
37
- margin: -0.125rem -0.375rem 0 0;
37
+ margin: -0.125rem 0 0;
38
+ margin-inline-end: -0.375rem;
38
39
  padding: 0;
39
40
  color: inherit;
40
41
  background: transparent;
@@ -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-right: var(--ray-space-3);
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
- left: 0;
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
- right: calc(var(--ray-space-2) + 4px);
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) 0 0 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
- left: 0;
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
- left: calc(50% + 0.375rem);
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; left: calc(50% + 0.5rem); }
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 { left: var(--ray-space-2); }
78
- .next { right: var(--ray-space-2); }
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 = v ? vp.scrollTop : vp.scrollLeft;
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 vp = this.viewport;
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
- if (e.key === fwd) {
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 (e.key === back) {
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
- :host([label-position="left"]) .root { flex-direction: row-reverse; justify-content: flex-end; }
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: "right" },
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-left: var(--ray-space-1); font-weight: 400; color: var(--ray-muted-foreground); font-variant-numeric: tabular-nums; }
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. */
@@ -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 var(--ray-space-2) 0 var(--ray-space-4);
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); right: var(--ray-space-2); background: var(--ray-code-background); opacity: 0; transition: opacity var(--ray-duration-fast); }
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
- .scroller { overflow: auto; max-height: var(--_max, none); outline: none; }
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-left: 0; }
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: right;
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-left: calc(var(--_digits, 2) * 1ch + var(--ray-space-6)); text-indent: calc((var(--_digits, 2) * 1ch + var(--ray-space-6)) * -1); }
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); }
@@ -398,6 +400,7 @@ export class RayCode extends RayElement {
398
400
  lineNumbers: { type: Boolean, reflect: true },
399
401
  highlight: { type: String },
400
402
  wrap: { type: Boolean, reflect: true },
403
+ verbatim: { type: Boolean, reflect: true },
401
404
  maxHeight: { type: String },
402
405
  expanded: { type: Boolean, reflect: true },
403
406
  hideCopy: { type: Boolean, reflect: true },
@@ -448,20 +451,22 @@ export class RayCode extends RayElement {
448
451
  this.#observer.disconnect();
449
452
  }
450
453
 
451
- /** The code shown (dedented), whatever its source. */
454
+ /** The code shown (dedented unless verbatim), whatever its source. */
452
455
  get source() {
453
456
  return this.#source;
454
457
  }
455
458
 
456
459
  #readSource() {
457
- if (typeof this.code === "string") return dedent(this.code);
460
+ // verbatim: data (a response body, a log) is shown and copied as it came.
461
+ const shape = this.verbatim ? String : dedent;
462
+ if (typeof this.code === "string") return shape(this.code);
458
463
  // A <template> is parsed as HTML: perfect for markup; its serialization
459
464
  // escapes &, < and > in text, undone here.
460
465
  const tpl = this.querySelector(":scope > template");
461
- if (tpl) return dedent(tpl.innerHTML.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&nbsp;/g, "\u00a0").replace(/&amp;/g, "&"));
466
+ if (tpl) return shape(tpl.innerHTML.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&nbsp;/g, "\u00a0").replace(/&amp;/g, "&"));
462
467
  const script = this.querySelector(':scope > script[type="text/plain"]');
463
- if (script) return dedent(script.textContent);
464
- return dedent(this.textContent);
468
+ if (script) return shape(script.textContent);
469
+ return shape(this.textContent);
465
470
  }
466
471
 
467
472
  update() {
@@ -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 0.125rem 0 0.5rem;
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);
@@ -86,6 +86,7 @@ const styles = css`
86
86
  .icon { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; color: var(--ray-muted-foreground); }
87
87
  .option[aria-selected="true"] .icon { color: inherit; }
88
88
  .icon svg { width: 1rem; height: 1rem; }
89
+ .prefix { flex: none; display: inline-flex; align-items: center; font-size: var(--ray-font-size-xs); color: var(--ray-muted-foreground); }
89
90
  .text { display: flex; flex: 1; flex-direction: column; min-width: 0; }
90
91
  .label, .description { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
91
92
  .label { font-size: var(--ray-font-size-sm); }
@@ -110,7 +111,7 @@ const styles = css`
110
111
  .hint b {
111
112
  display: inline-block;
112
113
  min-width: 1.25rem;
113
- margin-right: var(--ray-space-1);
114
+ margin-inline-end: var(--ray-space-1);
114
115
  padding: 0 0.3rem;
115
116
  font-family: var(--ray-font-mono);
116
117
  font-size: 0.6875rem;
@@ -187,6 +188,9 @@ export class RayCommandPalette extends RayElement {
187
188
  emptyLabel: { type: String, default: "Nothing found" },
188
189
  label: { type: String, default: "Command palette" },
189
190
  hideHint: { type: Boolean, reflect: true },
191
+ navigateLabel: { type: String, default: "navigate" },
192
+ runLabel: { type: String, default: "run" },
193
+ closeLabel: { type: String, default: "close" },
190
194
  overlayBlur: { type: String },
191
195
  nativeMenus: { type: Boolean, attribute: "native-menus", reflect: true },
192
196
  };
@@ -196,7 +200,7 @@ export class RayCommandPalette extends RayElement {
196
200
  <div class="search" part="search">${SEARCH_ICON}<input id="input" part="input" role="combobox" aria-expanded="true" aria-controls="list" aria-autocomplete="list" autocomplete="off" spellcheck="false"></div>
197
201
  <div class="list" id="list" role="listbox" part="list"></div>
198
202
  <div class="empty" part="empty" hidden></div>
199
- <div class="hint" part="hint"><span><b>↑↓</b> navigate</span><span><b>↵</b> run</span><span><b>esc</b> close</span></div>
203
+ <div class="hint" part="hint"><span><b>↑↓</b> <span id="hint-navigate"></span></span><span><b>↵</b> <span id="hint-run"></span></span><span><b>esc</b> <span id="hint-close"></span></span></div>
200
204
  <slot name="overlay"></slot>
201
205
  </dialog>`;
202
206
 
@@ -281,6 +285,9 @@ export class RayCommandPalette extends RayElement {
281
285
  this.dialog.setAttribute("aria-label", this.label ?? "Command palette");
282
286
  if (this.input.value !== (this.query ?? "")) this.input.value = this.query ?? "";
283
287
  this.$(".hint").hidden = Boolean(this.hideHint);
288
+ this.$("#hint-navigate").textContent = this.navigateLabel ?? "";
289
+ this.$("#hint-run").textContent = this.runLabel ?? "";
290
+ this.$("#hint-close").textContent = this.closeLabel ?? "";
284
291
  this._render();
285
292
  if (changed.has("open") || this._first === undefined) {
286
293
  this._first = false;
@@ -399,6 +406,14 @@ export class RayCommandPalette extends RayElement {
399
406
  else icon.textContent = String(a.icon);
400
407
  opt.append(icon);
401
408
  }
409
+ // Content of its own before the text, any width: a colored method tag.
410
+ if (a.prefix !== undefined && a.prefix !== null && a.prefix !== "") {
411
+ const prefix = document.createElement("span");
412
+ prefix.className = "prefix";
413
+ if (a.prefix instanceof Node) prefix.append(a.prefix);
414
+ else prefix.textContent = String(a.prefix);
415
+ opt.append(prefix);
416
+ }
402
417
  const text = document.createElement("span");
403
418
  text.className = "text";
404
419
  const label = document.createElement("span");
@@ -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
- switch (e.key) {
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: left;
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-right: calc(var(--_px) * 0.4);
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-left: auto;
69
+ margin-inline-start: auto;
70
70
  padding: 0;
71
71
  color: var(--ray-muted-foreground);
72
72
  background: transparent;