@raylang/elements 0.0.0-stage → 0.3.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 (95) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +4 -0
  3. package/README.md +12 -2
  4. package/base.css +111 -0
  5. package/components/accordion/accordion.js +197 -0
  6. package/components/alert/alert.js +112 -0
  7. package/components/app-bar/app-bar.js +168 -0
  8. package/components/app-shell/app-shell.js +198 -0
  9. package/components/aspect-ratio/aspect-ratio.js +59 -0
  10. package/components/attachment/attachment.js +333 -0
  11. package/components/avatar/avatar.js +96 -0
  12. package/components/badge/badge.js +63 -0
  13. package/components/blockquote/blockquote.js +128 -0
  14. package/components/bottom-nav/bottom-nav.js +309 -0
  15. package/components/breadcrumbs/breadcrumbs.js +175 -0
  16. package/components/button/button.js +283 -0
  17. package/components/button-group/button-group.js +72 -0
  18. package/components/card/card.js +73 -0
  19. package/components/carousel/carousel.js +435 -0
  20. package/components/center/center.js +26 -0
  21. package/components/checkbox/checkbox.js +152 -0
  22. package/components/checkbox-group/checkbox-group.js +152 -0
  23. package/components/code/code.js +574 -0
  24. package/components/color-picker/color-picker.js +775 -0
  25. package/components/combobox/combobox.js +563 -0
  26. package/components/command-palette/command-palette.js +510 -0
  27. package/components/container/container.js +41 -0
  28. package/components/date-picker/date-picker.js +594 -0
  29. package/components/dialog/dialog.js +329 -0
  30. package/components/drawer/drawer.js +139 -0
  31. package/components/empty-state/empty-state.js +66 -0
  32. package/components/file-upload/file-upload.js +589 -0
  33. package/components/grid/grid.js +96 -0
  34. package/components/group/group.js +40 -0
  35. package/components/icon/icon.js +58 -0
  36. package/components/icon-button/icon-button.js +30 -0
  37. package/components/indicator/indicator.js +112 -0
  38. package/components/input/input.js +85 -0
  39. package/components/kbd/kbd.js +60 -0
  40. package/components/menu/menu.js +296 -0
  41. package/components/nav-link/nav-link.js +159 -0
  42. package/components/notification/notification.js +591 -0
  43. package/components/number-input/number-input.js +241 -0
  44. package/components/pagination/pagination.js +194 -0
  45. package/components/password-input/password-input.js +85 -0
  46. package/components/path-input/path-input.js +137 -0
  47. package/components/popover/popover.js +85 -0
  48. package/components/progress/progress.js +109 -0
  49. package/components/radio/radio.js +253 -0
  50. package/components/scroll-area/scroll-area.js +158 -0
  51. package/components/search/search.js +157 -0
  52. package/components/segmented-control/segmented-control.js +190 -0
  53. package/components/select/select.js +506 -0
  54. package/components/separator/separator.js +58 -0
  55. package/components/sidebar/sidebar.js +252 -0
  56. package/components/skeleton/skeleton.js +90 -0
  57. package/components/slider/slider.js +155 -0
  58. package/components/spinner/spinner.js +39 -0
  59. package/components/split/split.js +221 -0
  60. package/components/stack/stack.js +46 -0
  61. package/components/stat/stat.js +79 -0
  62. package/components/statusbar/statusbar.js +110 -0
  63. package/components/stepper/stepper.js +239 -0
  64. package/components/switch/switch.js +45 -0
  65. package/components/table/table.js +328 -0
  66. package/components/tabs/tabs.js +224 -0
  67. package/components/textarea/textarea.js +105 -0
  68. package/components/theme-toggle/theme-toggle.js +78 -0
  69. package/components/timeline/timeline.js +150 -0
  70. package/components/toggle/toggle.js +130 -0
  71. package/components/toggle-group/toggle-group.js +216 -0
  72. package/components/tooltip/tooltip.js +101 -0
  73. package/core/charts.js +107 -0
  74. package/core/color.js +110 -0
  75. package/core/core.js +556 -0
  76. package/core/dismiss.js +69 -0
  77. package/core/floating.js +195 -0
  78. package/core/focus.js +70 -0
  79. package/core/form.js +235 -0
  80. package/core/host.js +200 -0
  81. package/core/hotkey.js +40 -0
  82. package/core/icons.js +104 -0
  83. package/core/motion.js +73 -0
  84. package/core/position.js +91 -0
  85. package/core/raylang.js +167 -0
  86. package/core/scroll.js +58 -0
  87. package/core/styles.js +211 -0
  88. package/core.js +16 -0
  89. package/custom-elements.json +18545 -0
  90. package/index.js +74 -0
  91. package/package.json +312 -4
  92. package/ray-elements.d.ts +2570 -0
  93. package/tokens.css +796 -0
  94. package/tokens.json +2083 -0
  95. package/vscode.html-custom-data.json +5499 -0
@@ -0,0 +1,157 @@
1
+ // <ray-search>: a search box (shadcn's command input, Mantine's Spotlight
2
+ // field). A RayInput — label, form value, validity — with the search icon,
3
+ // a clear button, an optional keyboard shortcut that focuses it (shown as a
4
+ // hint while empty) and a debounced ray-search event.
5
+
6
+ import { css, define } from "../../core/core.js";
7
+ import { RayInput } from "../input/input.js";
8
+ import { fieldTemplate } from "../../core/form.js";
9
+ import { matchesHotkey, formatHotkey, isTyping } from "../../core/hotkey.js";
10
+
11
+ const styles = css`
12
+ .box { gap: 0; }
13
+ /* variant="filled" is the default here (defaults are not reflected as attributes). */
14
+ :host(:not([variant])) .box { background: var(--ray-muted); border-color: transparent; }
15
+ .lead {
16
+ display: inline-flex;
17
+ padding-left: calc(var(--_px) * 0.66);
18
+ color: var(--ray-muted-foreground);
19
+ pointer-events: none;
20
+ }
21
+ .lead svg { width: 1.1em; height: 1.1em; }
22
+ .control { padding-left: calc(var(--_px) * 0.5); }
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-right: calc(var(--_px) * 0.4); }
25
+ .clear {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ width: calc(var(--_h) * 0.6);
30
+ height: calc(var(--_h) * 0.6);
31
+ padding: 0;
32
+ color: var(--ray-muted-foreground);
33
+ background: transparent;
34
+ border: 0;
35
+ border-radius: var(--ray-radius-sm);
36
+ cursor: pointer;
37
+ }
38
+ .clear:hover { color: var(--ray-foreground); background: var(--ray-default-hover); }
39
+ .clear svg { width: 0.9em; height: 0.9em; }
40
+ .clear[hidden], .shortcut[hidden] { display: none; }
41
+ .shortcut {
42
+ padding: 0 0.375rem;
43
+ font-family: var(--ray-font-mono);
44
+ font-size: 0.6875rem;
45
+ line-height: 1.25rem;
46
+ color: var(--ray-muted-foreground);
47
+ background: var(--ray-background);
48
+ border: 1px solid var(--ray-border);
49
+ border-radius: var(--ray-radius-sm);
50
+ white-space: nowrap;
51
+ }
52
+ `;
53
+
54
+ export class RaySearch extends RayInput {
55
+ static properties = {
56
+ ...RayInput.properties,
57
+ type: { type: String, reflect: true, default: "search" },
58
+ variant: { type: String, reflect: true, default: "filled" },
59
+ placeholder: { type: String, default: "Search…" },
60
+ shortcut: { type: String },
61
+ debounce: { type: Number, default: 200 },
62
+ };
63
+ static styles = [...RayInput.styles, styles];
64
+ static template = fieldTemplate(
65
+ `<span class="lead" part="icon" aria-hidden="true" data-ray-icon="search"></span>` +
66
+ `<input id="control" class="control" part="input" type="search" enterkeyhint="search" autocomplete="off" spellcheck="false">` +
67
+ `<span class="tail"><button class="clear focus-ring" part="clear" type="button" aria-label="Clear search" tabindex="-1" hidden><span data-ray-icon="close" aria-hidden="true"></span></button>` +
68
+ `<kbd class="shortcut" part="shortcut" aria-hidden="true" hidden></kbd></span>`,
69
+ );
70
+
71
+ #timer = 0;
72
+
73
+ constructor() {
74
+ super();
75
+ this._onHotkey = (e) => {
76
+ if (!this.shortcut || e.defaultPrevented || !matchesHotkey(e, this.shortcut)) return;
77
+ // A one-key shortcut ("/") must not fire while typing elsewhere.
78
+ if (!/[+]/.test(this.shortcut) && isTyping(e)) return;
79
+ e.preventDefault();
80
+ this.control.focus();
81
+ this.control.select();
82
+ };
83
+ }
84
+
85
+ wire() {
86
+ super.wire();
87
+ this.control.addEventListener("input", () => this._schedule());
88
+ this.control.addEventListener("keydown", (e) => {
89
+ if (e.key === "Escape" && this.control.value) {
90
+ e.preventDefault();
91
+ e.stopPropagation();
92
+ this.clear();
93
+ } else if (e.key === "Enter") {
94
+ this._emit();
95
+ }
96
+ });
97
+ this.control.addEventListener("focus", () => this._paintTail());
98
+ this.control.addEventListener("blur", () => this._paintTail());
99
+ this.$(".clear").addEventListener("click", () => {
100
+ this.clear();
101
+ this.control.focus();
102
+ });
103
+ }
104
+
105
+ connectedCallback() {
106
+ super.connectedCallback();
107
+ document.addEventListener("keydown", this._onHotkey);
108
+ }
109
+
110
+ disconnectedCallback() {
111
+ super.disconnectedCallback();
112
+ document.removeEventListener("keydown", this._onHotkey);
113
+ clearTimeout(this.#timer);
114
+ }
115
+
116
+ update(changed) {
117
+ super.update(changed);
118
+ // The placeholder attribute (forwarded by RayInput) wins over the default.
119
+ const ph = this.getAttribute("placeholder") ?? this.placeholder ?? "";
120
+ if (this.control.placeholder !== ph) this.control.placeholder = ph;
121
+ this.$(".shortcut").textContent = this.shortcut ? formatHotkey(this.shortcut) : "";
122
+ this._paintTail();
123
+ }
124
+
125
+ /** Empties the box: fires input, then ray-search with "". */
126
+ clear() {
127
+ if (!this.control.value) return;
128
+ this.control.value = "";
129
+ this.commitProperty("value", "");
130
+ this._commit();
131
+ this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
132
+ this._emit();
133
+ }
134
+
135
+ // Clear button while there is text; the shortcut hint while empty and unfocused.
136
+ _paintTail() {
137
+ const has = Boolean(this.control.value);
138
+ this.$(".clear").hidden = !has || this.effectivelyDisabled || Boolean(this.readonly);
139
+ const focused = this.shadowRoot.activeElement === this.control;
140
+ this.$(".shortcut").hidden = !this.shortcut || has || focused;
141
+ }
142
+
143
+ _schedule() {
144
+ this._paintTail();
145
+ clearTimeout(this.#timer);
146
+ const wait = Math.max(0, Number(this.debounce) || 0);
147
+ if (wait === 0) this._emit();
148
+ else this.#timer = setTimeout(() => this._emit(), wait);
149
+ }
150
+
151
+ _emit() {
152
+ clearTimeout(this.#timer);
153
+ this.dispatchEvent(new CustomEvent("ray-search", { bubbles: true, composed: true, detail: { value: this.control.value } }));
154
+ }
155
+ }
156
+
157
+ define("ray-search", RaySearch);
@@ -0,0 +1,190 @@
1
+ // <ray-segmented-control>: one choice among a few, as a segmented button row
2
+ // with a sliding indicator (Mantine's SegmentedControl). Options from child
3
+ // <option> elements or the `data` property. Form-associated.
4
+
5
+ import { css, define } from "../../core/core.js";
6
+ import { applyColor, sizeStyles, radiusStyles } from "../../core/styles.js";
7
+ import { FormControl } from "../../core/form.js";
8
+
9
+ const styles = css`
10
+ :host { display: inline-flex; font-size: var(--_fs); }
11
+ :host([full-width]) { display: flex; }
12
+ .root {
13
+ position: relative;
14
+ display: inline-flex;
15
+ flex: 1;
16
+ padding: 0.25rem;
17
+ background: var(--ray-muted);
18
+ border: 1px solid var(--ray-border);
19
+ border-radius: var(--_r);
20
+ }
21
+ :host([orientation="vertical"]) .root { flex-direction: column; }
22
+ .indicator {
23
+ position: absolute;
24
+ top: 0;
25
+ left: 0;
26
+ z-index: 0;
27
+ box-sizing: border-box;
28
+ background: var(--ray-raised);
29
+ border: 1px solid var(--ray-border);
30
+ border-radius: calc(var(--_r) - 2px);
31
+ box-shadow: var(--ray-shadow-xs);
32
+ transition: transform var(--ray-duration-base) var(--ray-easing-standard), width var(--ray-duration-base) var(--ray-easing-standard), height var(--ray-duration-base) var(--ray-easing-standard);
33
+ }
34
+ :host([color]) .indicator { background: var(--_filled); border-color: var(--_filled); }
35
+ .indicator[hidden] { display: none; }
36
+ button {
37
+ position: relative;
38
+ z-index: 1;
39
+ flex: 1;
40
+ min-height: calc(var(--_h) - 0.5rem - 2px);
41
+ padding: 0 var(--_px);
42
+ font: inherit;
43
+ font-size: var(--_fs);
44
+ font-weight: var(--ray-font-weight-medium);
45
+ color: var(--ray-muted-foreground);
46
+ white-space: nowrap;
47
+ background: transparent;
48
+ border: 0;
49
+ border-radius: calc(var(--_r) - 2px);
50
+ cursor: pointer;
51
+ transition: color var(--ray-duration-fast) var(--ray-easing-standard);
52
+ }
53
+ button:hover:not([aria-checked="true"]):not(:disabled) { color: var(--ray-foreground); }
54
+ button[aria-checked="true"] { color: var(--ray-foreground); }
55
+ :host([color]) button[aria-checked="true"] { color: var(--_contrast); }
56
+ button:disabled { cursor: not-allowed; opacity: 0.5; }
57
+ :host([disabled]) .root { opacity: 0.6; }
58
+ .options { display: none; }
59
+ `;
60
+
61
+ export class RaySegmentedControl extends FormControl {
62
+ static delegatesFocus = false;
63
+ static properties = {
64
+ name: { type: String, reflect: true },
65
+ value: { type: String },
66
+ data: { attribute: false },
67
+ disabled: { type: Boolean, reflect: true },
68
+ size: { type: String, reflect: true, default: "sm" },
69
+ radius: { type: String, reflect: true },
70
+ color: { type: String, reflect: true },
71
+ fullWidth: { type: Boolean, reflect: true },
72
+ orientation: { type: String, reflect: true, default: "horizontal" },
73
+ };
74
+ static styles = [sizeStyles, radiusStyles, styles];
75
+ static template = `<div class="root" part="root" role="radiogroup"><span class="indicator" part="indicator" hidden></span></div><div class="options"><slot></slot></div>`;
76
+
77
+ constructor() {
78
+ super();
79
+ this.root = this.$(".root");
80
+ this.$("slot").addEventListener("slotchange", () => this._render());
81
+ this.root.addEventListener("click", (e) => {
82
+ const b = e.target.closest("button");
83
+ if (b && !b.disabled) this._choose(b.dataset.value, true);
84
+ });
85
+ this.root.addEventListener("keydown", (e) => this._key(e));
86
+ if (typeof ResizeObserver === "function") this._ro = new ResizeObserver(() => this._moveIndicator());
87
+ }
88
+
89
+ connectedCallback() {
90
+ super.connectedCallback();
91
+ this._ro?.observe(this.root);
92
+ if (this._default === undefined) this._default = this.getAttribute("value");
93
+ }
94
+
95
+ disconnectedCallback() {
96
+ super.disconnectedCallback();
97
+ this._ro?.disconnect();
98
+ }
99
+
100
+ /** [{ value, label, disabled }] from `data` or the child options. */
101
+ get items() {
102
+ if (Array.isArray(this.data)) {
103
+ return this.data.map((d) => (typeof d === "string" ? { value: d, label: d, disabled: false } : { value: d.value, label: d.label ?? d.value, disabled: Boolean(d.disabled) }));
104
+ }
105
+ return [...this.querySelectorAll("option")].map((o) => ({ value: o.value, label: o.textContent, disabled: o.disabled }));
106
+ }
107
+
108
+ update(changed) {
109
+ if (changed.has("data") || !this._buttons) this._render();
110
+ const items = this.items;
111
+ if (!items.some((i) => i.value === this.value && !i.disabled)) {
112
+ const first = items.find((i) => !i.disabled);
113
+ if (first) this.commitProperty("value", first.value);
114
+ }
115
+ const disabled = this.effectivelyDisabled;
116
+ for (const b of this._buttons) {
117
+ const on = b.dataset.value === this.value;
118
+ b.setAttribute("aria-checked", String(on));
119
+ b.tabIndex = on && !disabled ? 0 : -1;
120
+ b.disabled = disabled || b.dataset.disabled === "true";
121
+ }
122
+ applyColor(this.root, this.color);
123
+ this.root.setAttribute("aria-orientation", this.orientation === "vertical" ? "vertical" : "horizontal");
124
+ const label = this.getAttribute("aria-label");
125
+ if (label) this.root.setAttribute("aria-label", label);
126
+ this.internals.setFormValue(this.value ?? null);
127
+ this._moveIndicator();
128
+ }
129
+
130
+ _render() {
131
+ for (const b of this._buttons ?? []) b.remove();
132
+ this._buttons = this.items.map((item) => {
133
+ const b = document.createElement("button");
134
+ b.type = "button";
135
+ b.setAttribute("role", "radio");
136
+ b.setAttribute("part", "control");
137
+ b.className = "focus-ring";
138
+ b.dataset.value = item.value;
139
+ b.dataset.disabled = String(item.disabled);
140
+ b.textContent = item.label;
141
+ this.root.append(b);
142
+ return b;
143
+ });
144
+ if (this.isConnected) this.requestUpdate();
145
+ }
146
+
147
+ _moveIndicator() {
148
+ const ind = this.$(".indicator");
149
+ const b = this._buttons?.find((x) => x.dataset.value === this.value);
150
+ if (!b || !b.offsetWidth) {
151
+ ind.hidden = true;
152
+ return;
153
+ }
154
+ ind.hidden = false;
155
+ ind.style.width = `${b.offsetWidth}px`;
156
+ ind.style.height = `${b.offsetHeight}px`;
157
+ ind.style.transform = `translate(${b.offsetLeft}px, ${b.offsetTop}px)`;
158
+ }
159
+
160
+ _choose(value, user) {
161
+ const changed = value !== this.value;
162
+ this.value = value;
163
+ this._buttons.find((b) => b.dataset.value === value)?.focus();
164
+ if (user && changed) {
165
+ this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
166
+ this.dispatchEvent(new Event("change", { bubbles: true }));
167
+ }
168
+ }
169
+
170
+ _key(e) {
171
+ const enabled = this._buttons.filter((b) => !b.disabled);
172
+ const i = enabled.findIndex((b) => b.dataset.value === this.value);
173
+ let next = null;
174
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") next = enabled[(i + 1) % enabled.length];
175
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = enabled[(i - 1 + enabled.length) % enabled.length];
176
+ if (!next) return;
177
+ e.preventDefault();
178
+ this._choose(next.dataset.value, true);
179
+ }
180
+
181
+ formResetCallback() {
182
+ this.value = this._default ?? undefined;
183
+ }
184
+
185
+ formStateRestoreCallback(state) {
186
+ this.value = state ?? undefined;
187
+ }
188
+ }
189
+
190
+ define("ray-segmented-control", RaySegmentedControl);