@rvx/ui 0.1.6
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/LICENSE +21 -0
- package/dist/common/events.d.ts +72 -0
- package/dist/common/events.js +58 -0
- package/dist/common/events.js.map +1 -0
- package/dist/common/parsers.d.ts +88 -0
- package/dist/common/parsers.js +62 -0
- package/dist/common/parsers.js.map +1 -0
- package/dist/common/theme-test.d.ts +7 -0
- package/dist/common/theme-test.js +14 -0
- package/dist/common/theme-test.js.map +1 -0
- package/dist/common/theme.d.ts +144 -0
- package/dist/common/theme.js +2 -0
- package/dist/common/theme.js.map +1 -0
- package/dist/common/trim.d.ts +12 -0
- package/dist/common/trim.js +16 -0
- package/dist/common/trim.js.map +1 -0
- package/dist/common/types.d.ts +13 -0
- package/dist/common/types.js +10 -0
- package/dist/common/types.js.map +1 -0
- package/dist/common/writing-mode.d.ts +82 -0
- package/dist/common/writing-mode.js +61 -0
- package/dist/common/writing-mode.js.map +1 -0
- package/dist/components/button.d.ts +42 -0
- package/dist/components/button.js +26 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/checkbox.d.ts +9 -0
- package/dist/components/checkbox.js +32 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/collapse-test.d.ts +8 -0
- package/dist/components/collapse-test.js +15 -0
- package/dist/components/collapse-test.js.map +1 -0
- package/dist/components/collapse.d.ts +13 -0
- package/dist/components/collapse.js +44 -0
- package/dist/components/collapse.js.map +1 -0
- package/dist/components/column.d.ts +12 -0
- package/dist/components/column.js +12 -0
- package/dist/components/column.js.map +1 -0
- package/dist/components/control-group.d.ts +7 -0
- package/dist/components/control-group.js +11 -0
- package/dist/components/control-group.js.map +1 -0
- package/dist/components/dialog.d.ts +33 -0
- package/dist/components/dialog.js +67 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-input.d.ts +27 -0
- package/dist/components/dropdown-input.js +31 -0
- package/dist/components/dropdown-input.js.map +1 -0
- package/dist/components/dropdown.d.ts +123 -0
- package/dist/components/dropdown.js +176 -0
- package/dist/components/dropdown.js.map +1 -0
- package/dist/components/flex-space.d.ts +4 -0
- package/dist/components/flex-space.js +10 -0
- package/dist/components/flex-space.js.map +1 -0
- package/dist/components/heading.d.ts +9 -0
- package/dist/components/heading.js +14 -0
- package/dist/components/heading.js.map +1 -0
- package/dist/components/label.d.ts +14 -0
- package/dist/components/label.js +15 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/layer.d.ts +81 -0
- package/dist/components/layer.js +164 -0
- package/dist/components/layer.js.map +1 -0
- package/dist/components/link.d.ts +57 -0
- package/dist/components/link.js +26 -0
- package/dist/components/link.js.map +1 -0
- package/dist/components/page.d.ts +9 -0
- package/dist/components/page.js +17 -0
- package/dist/components/page.js.map +1 -0
- package/dist/components/popout.d.ts +134 -0
- package/dist/components/popout.js +259 -0
- package/dist/components/popout.js.map +1 -0
- package/dist/components/popover.d.ts +139 -0
- package/dist/components/popover.js +101 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/radio-buttons.d.ts +17 -0
- package/dist/components/radio-buttons.js +26 -0
- package/dist/components/radio-buttons.js.map +1 -0
- package/dist/components/row.d.ts +10 -0
- package/dist/components/row.js +23 -0
- package/dist/components/row.js.map +1 -0
- package/dist/components/scroll-view.d.ts +6 -0
- package/dist/components/scroll-view.js +72 -0
- package/dist/components/scroll-view.js.map +1 -0
- package/dist/components/text-input.d.ts +53 -0
- package/dist/components/text-input.js +35 -0
- package/dist/components/text-input.js.map +1 -0
- package/dist/components/text.d.ts +7 -0
- package/dist/components/text.js +11 -0
- package/dist/components/text.js.map +1 -0
- package/dist/components/validation.d.ts +109 -0
- package/dist/components/validation.js +151 -0
- package/dist/components/validation.js.map +1 -0
- package/dist/components/value.d.ts +7 -0
- package/dist/components/value.js +11 -0
- package/dist/components/value.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.js +3 -0
- package/dist/test.js.map +1 -0
- package/dist/theme.module.css +679 -0
- package/dist/theme.module.css.map +1 -0
- package/package.json +29 -0
- package/src/common/events.tsx +130 -0
- package/src/common/parsers.tsx +167 -0
- package/src/common/theme-test.tsx +20 -0
- package/src/common/theme.tsx +165 -0
- package/src/common/trim.tsx +30 -0
- package/src/common/types.tsx +23 -0
- package/src/common/writing-mode.tsx +150 -0
- package/src/components/button.tsx +94 -0
- package/src/components/checkbox.tsx +64 -0
- package/src/components/collapse-test.tsx +23 -0
- package/src/components/collapse.tsx +75 -0
- package/src/components/column.tsx +28 -0
- package/src/components/control-group.tsx +22 -0
- package/src/components/dialog.tsx +137 -0
- package/src/components/dropdown-input.tsx +82 -0
- package/src/components/dropdown.tsx +352 -0
- package/src/components/flex-space.tsx +15 -0
- package/src/components/heading.tsx +23 -0
- package/src/components/label.tsx +37 -0
- package/src/components/layer.tsx +299 -0
- package/src/components/link.tsx +118 -0
- package/src/components/page.tsx +36 -0
- package/src/components/popout.tsx +461 -0
- package/src/components/popover.tsx +292 -0
- package/src/components/radio-buttons.tsx +81 -0
- package/src/components/row.tsx +37 -0
- package/src/components/scroll-view.tsx +97 -0
- package/src/components/text-input.tsx +117 -0
- package/src/components/text.tsx +22 -0
- package/src/components/validation.tsx +272 -0
- package/src/components/value.tsx +22 -0
- package/src/index.tsx +29 -0
- package/src/test.tsx +2 -0
- package/src/theme/base.scss +69 -0
- package/src/theme/common.scss +51 -0
- package/src/theme/components/button.scss +116 -0
- package/src/theme/components/checkbox.scss +25 -0
- package/src/theme/components/collapse.scss +64 -0
- package/src/theme/components/column.scss +28 -0
- package/src/theme/components/control-group.scss +14 -0
- package/src/theme/components/dialog.scss +44 -0
- package/src/theme/components/dropdown.scss +50 -0
- package/src/theme/components/flex-space.scss +6 -0
- package/src/theme/components/heading.scss +39 -0
- package/src/theme/components/label.scss +24 -0
- package/src/theme/components/link.scss +25 -0
- package/src/theme/components/page.scss +22 -0
- package/src/theme/components/popover.scss +58 -0
- package/src/theme/components/radio-buttons.scss +31 -0
- package/src/theme/components/row.scss +17 -0
- package/src/theme/components/scroll-view.scss +51 -0
- package/src/theme/components/text-input.scss +45 -0
- package/src/theme/components/text.scss +12 -0
- package/src/theme/components/validation.scss +15 -0
- package/src/theme/components/value.scss +4 -0
- package/src/theme/theme.scss +22 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sourceRoot":"","sources":["../src/theme/base.scss","../src/theme/common.scss","../src/theme/components/button.scss","../src/theme/components/checkbox.scss","../src/theme/components/collapse.scss","../src/theme/components/column.scss","../src/theme/components/control-group.scss","../src/theme/components/dialog.scss","../src/theme/components/dropdown.scss","../src/theme/components/flex-space.scss","../src/theme/components/heading.scss","../src/theme/components/label.scss","../src/theme/components/link.scss","../src/theme/components/page.scss","../src/theme/components/popover.scss","../src/theme/components/radio-buttons.scss","../src/theme/components/row.scss","../src/theme/components/scroll-view.scss","../src/theme/components/text-input.scss","../src/theme/components/text.scss","../src/theme/components/validation.scss","../src/theme/components/value.scss"],"names":[],"mappings":"AASA;EACC;EACA;EACA;EAEA;EACA;EACA;EACA;ECWA;EACA;EACA;EACA;EDXA;EACA;EACA;EACA;EACA;ECIA;EACA;EACA;EACA;EDJA;EAEA;EAEA;EACA;EAEA;;;ACrBC;EAFD;IANE;IAAA;IAAA;IAUA;;;ADkCH;EACC;EACA;EAEA;;AACA;EALD;IAME;;;;AAIF;EACC;EACA;EACA;EACA;;;AAGD;EACC;;;ACtDC;EAFD;IANE;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAUA;;;ACmEH;EACC;EACA;EACA;EACA;EAEA;EAEA;EDtDC,eACC;EAED,gBACC;ECqDF;;AAEA;EACC;EACA;;;AAKD;EACC;EACA;EACA;;AACA;EAEC;;;AANF;EACC;EACA;EACA;;AACA;EAEC;;;AANF;EACC;EACA;EACA;;AACA;EAEC;;;AANF;EACC;EACA;EACA;;AACA;EAEC;;;AANF;EACC;EACA;EACA;;AACA;EAEC;;;AANF;EACC;EACA;EACA;;AACA;EAEC;;;AAKH;EACC;;;ACjHD;EACC;EACA;EACA;;AAEA;EACC;;AAGD;EACC;;AAGD;EACC;EACA;;;AAIF;EACC;EACA;EACA;;;ACtBD;EACC;EACA;EACA;;;AAGD;EACC,YACC;;;AAIF;EACC;IAAO;;EACP;IAAM;;EACN;IAAM;;EACN;IAAK;;;AAGN;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;;AAEA;EACC,YACC;;AAIF;EACC;EACA;;;AAIF;EACC;EACA;EACA;EACA;;AAEA;EACC,YACC;;AAKF;EACC;EACA;EACA;;;AC5DF;EACC;EACA;;AAEA;EACC;;AAMD;EACC;;;AAQD;EACC;;AACA;EACC;;;AAHF;EACC;;AACA;EACC;;;ACvBH;EACC;EACA;EACA;;AACA;EACC;EACA;;AAED;EACC;EACA;;;ALEA;EAFD;IANE;IAAA;IAAA;IAAA;IAUA;;;AMEH;EACC;EACA;EAEA;EAEA;EACA;EACA;EAEA;;;AAGD;EACC;EACA;EAEA;EACA;EACA;EACA;ENDC,eACC;EAED,gBACC;;;AMCH;EACC;;;AN7BC;EAFD;IANE;IAAA;IAAA;IAAA;IAUA;;;AOEH;EACC;EACA;;AAEA;EACC;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;EPPC,eACC;EAED,gBACC;;AOMF;EACC;;;AC9CF;EACC;EACA;EACA;;;ACHD;EACC;EACA;;AAEA;EAEC;EACA;EACA;EACA;;;AAIF;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;AAAA;AAAA;EAGC;EACA;EACA;;;ATxBC;EAFD;IANE;IAUA;;;AUPH;EACC;EACA;EAEA;EACA;EACA;;AAEA;EAEC;EACA;EACA;EACA;;;AVRA;EAFD;IANE;IAUA;;;AWPH;EACC;EACA;EAEA;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;;;ACrBF;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AZPC;EAFD;IANE;IAAA;IAAA;IAUA;;;AaDH;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EAEA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EbnBC,eACC;EAED,gBACC;;;AcvCH;EACC;EACA;EACA;;AAEA;EACC;EACA;;;AAIF;EACC;EACA;EACA;;AAEA;EACC;;AAGD;EACC;;;AAIF;EACC;EACA;EACA;;;AC5BD;EACC;EACA;EACA;;;AAIA;EACC;EACA;;AACA;EACC;EACA;;;AALF;EACC;EACA;;AACA;EACC;EACA;;;AfAD;EAFD;IANE;IAAA;IAUA;;;AgBJH;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;AAAA;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AhBpCC;EAFD;IANE;IAAA;IAAA;IAAA;IAUA;;;AiBEH;EACC;EACA;EACA;EAEA;EAEA;EjBmBC,eACC;EAED,gBACC;EiBrBF;EACA;EAEA;EACA;;AAEA;EACC;;AAGD;EACC;EACA;;;AAIF;EACC;;;AC1CD;EACC;;AAEA;EAEC;EACA;EACA;EACA;;;AlBIA;EAFD;IANE;IAUA;;;AmBPH;EACC;;;AAGD;EACC;;;ACZD;EACC","file":"theme.module.css"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rvx/ui",
|
|
3
|
+
"description": "Rvx based complete UI component library",
|
|
4
|
+
"homepage": "https://github.com/mxjp/rvx-ui",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/mxjp/rvx-ui"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"ui",
|
|
11
|
+
"ux",
|
|
12
|
+
"rvx"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"version": "0.1.6",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./dist/": "./dist/"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"rvx": "^16.0.2"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { waitFor } from "rvx/async";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get an identifier for the pressed key including any modifiers.
|
|
5
|
+
*
|
|
6
|
+
* The identifier is constructed by concatenating the modifies and lower cased key. In addition, the space key is represented as `space` for better readability.
|
|
7
|
+
*
|
|
8
|
+
* Modifiers in order:
|
|
9
|
+
* + `ctrl+`
|
|
10
|
+
* + `shift+`
|
|
11
|
+
* + `alt+`
|
|
12
|
+
* + `meta+`
|
|
13
|
+
*
|
|
14
|
+
* Examples:
|
|
15
|
+
* + `shift+a`
|
|
16
|
+
* + `ctrl+shift+space`
|
|
17
|
+
*
|
|
18
|
+
* @returns The identifier.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* switch (keyFor(event)) {
|
|
23
|
+
* case "enter": ...
|
|
24
|
+
* case "ctrl+a": ...
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function keyFor(event: KeyboardEvent): string {
|
|
29
|
+
let key = event.key.toLowerCase();
|
|
30
|
+
if (key === " ") {
|
|
31
|
+
key = "space";
|
|
32
|
+
}
|
|
33
|
+
if (event.metaKey) {
|
|
34
|
+
key = "meta+" + key;
|
|
35
|
+
}
|
|
36
|
+
if (event.altKey) {
|
|
37
|
+
key = "alt+" + key;
|
|
38
|
+
}
|
|
39
|
+
if (event.shiftKey) {
|
|
40
|
+
key = "shift+" + key;
|
|
41
|
+
}
|
|
42
|
+
if (event.ctrlKey) {
|
|
43
|
+
key = "ctrl+" + key;
|
|
44
|
+
}
|
|
45
|
+
return key;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A function to run an optionally async user action.
|
|
50
|
+
*
|
|
51
|
+
* Returning false indicates, that no action has been performed and that another action or the browser default behavior can be run.
|
|
52
|
+
*/
|
|
53
|
+
export type Action<T extends unknown[] = []> = (event: Event, ...args: T) => void | boolean | Promise<void>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Call an action that was triggered by the specified event.
|
|
57
|
+
*/
|
|
58
|
+
export function handleActionEvent<T extends unknown[]>(event: Event, action: Action<T>, ...args: T): boolean {
|
|
59
|
+
const result = action(event, ...args);
|
|
60
|
+
if (result === false) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
event.stopImmediatePropagation();
|
|
65
|
+
if (result instanceof Promise) {
|
|
66
|
+
waitFor(result);
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Run an action of the event target is hovered for a minimum duration.
|
|
73
|
+
*
|
|
74
|
+
* @param event The "mouseenter" event.
|
|
75
|
+
* @param action The action to run.
|
|
76
|
+
* @param delay The delay to wait for.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```tsx
|
|
80
|
+
* <button on:mouseenter={event => {
|
|
81
|
+
* startDelayedHoverOnMouseenter(event, () => {
|
|
82
|
+
* // Close things like popovers or dropdowns in the same context
|
|
83
|
+
* // when this button was hovered for some time:
|
|
84
|
+
* event.target.dispatchEvent(createPassiveActionEvent());
|
|
85
|
+
* });
|
|
86
|
+
* }}>Hover me!</button>
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function startDelayedHoverOnMouseenter(event: MouseEvent, action: () => void, delay = 300): void {
|
|
90
|
+
const timer = setTimeout(action, delay);
|
|
91
|
+
const cancel = () => {
|
|
92
|
+
event.target?.removeEventListener("mouseleave", cancel);
|
|
93
|
+
event.target?.removeEventListener("mousedown", cancel, { capture: true });
|
|
94
|
+
event.target?.removeEventListener("touchstart", cancel, { capture: true });
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
};
|
|
97
|
+
event.target?.addEventListener("mouseleave", cancel);
|
|
98
|
+
event.target?.addEventListener("mousedown", cancel, { capture: true });
|
|
99
|
+
event.target?.addEventListener("touchstart", cancel, { capture: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Rvx-ui's passive action event name.
|
|
104
|
+
*
|
|
105
|
+
* + This can be dispatched from an element to indicate that the user is likely no longer interested in interacting with other elements in the same context.
|
|
106
|
+
* + This is currently used to close nested dropdowns when the user hovers another dropdown item in the parent for some time.
|
|
107
|
+
*/
|
|
108
|
+
export const PASSIVE_ACTION_EVENT = "rvx-ui:passive-action";
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create a {@link PASSIVE_ACTION_EVENT} custom event.
|
|
112
|
+
*
|
|
113
|
+
* This event does not bubble.
|
|
114
|
+
*/
|
|
115
|
+
export function createPassiveActionEvent(): Event {
|
|
116
|
+
return new CustomEvent(PASSIVE_ACTION_EVENT);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Debounce an event handler.
|
|
121
|
+
*/
|
|
122
|
+
export function debounceEvent<T>(delayMs: number, handler: (event: T) => void): (event: T) => void {
|
|
123
|
+
let timer: number;
|
|
124
|
+
return event => {
|
|
125
|
+
if (timer) {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
}
|
|
128
|
+
timer = (setTimeout as typeof window.setTimeout)(() => handler(event), delayMs);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { sig, Signal, watchUpdates } from "rvx";
|
|
2
|
+
|
|
3
|
+
import { Validator } from "../components/validation.js";
|
|
4
|
+
|
|
5
|
+
export interface ValidParseResult<S> {
|
|
6
|
+
type: "valid";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The parsed value.
|
|
10
|
+
*/
|
|
11
|
+
value: S;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface InvalidParseResult<M> {
|
|
15
|
+
type: "invalid";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The validation message key.
|
|
19
|
+
*/
|
|
20
|
+
value: M;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ParseResult<S, M> = ValidParseResult<S> | InvalidParseResult<M>;
|
|
24
|
+
|
|
25
|
+
export class ParserFormatError extends TypeError {}
|
|
26
|
+
|
|
27
|
+
export interface Parser<S, I, M extends string> {
|
|
28
|
+
/**
|
|
29
|
+
* Parse the specified input.
|
|
30
|
+
*/
|
|
31
|
+
parse(input: I): ParseResult<S, M>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format the specified source.
|
|
35
|
+
*
|
|
36
|
+
* @throws A {@link ParserFormatError} if the source isn't currently formattable.
|
|
37
|
+
*/
|
|
38
|
+
format(source: S): I;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* An object with validation message keys and content to display.
|
|
42
|
+
*/
|
|
43
|
+
messages: Record<M, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Utility to get the source type from a parser.
|
|
48
|
+
*/
|
|
49
|
+
export type ParserSource<P extends Parser<any, any, any>> = P extends Parser<infer S, any, any> ? S : never;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Utility to get the input type from a parser.
|
|
53
|
+
*/
|
|
54
|
+
export type ParserInput<P extends Parser<any, any, any>> = P extends Parser<any, infer I, any> ? I : never;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Utility to get the message keys type from a parser.
|
|
58
|
+
*/
|
|
59
|
+
export type ParserMessages<P extends Parser<any, any, any>> = P extends Parser<any, any, infer M> ? M : never;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a bi-directional input signal with validation.
|
|
63
|
+
*
|
|
64
|
+
* @param source The source signal.
|
|
65
|
+
* @param parser The parser to use.
|
|
66
|
+
* @param input The initial input signal to use. If not specified, the parser is used to format the current source value.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* const value = sig(0);
|
|
71
|
+
*
|
|
72
|
+
* // Format the current value as initial value:
|
|
73
|
+
* <TextInput value={
|
|
74
|
+
* value
|
|
75
|
+
* .pipe(parse, intParser({ format: "Enter a valid number." }))
|
|
76
|
+
* .pipe(trim)
|
|
77
|
+
* } />
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function parse<S, P extends Parser<S, any, any>>(source: Signal<S>, parser: P, input?: Signal<ParserInput<P>>): Signal<ParserInput<P>> {
|
|
81
|
+
if (input === undefined) {
|
|
82
|
+
input = sig(parser.format(source.value));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const invalid = sig<undefined | ParserMessages<P>>(undefined);
|
|
86
|
+
|
|
87
|
+
watchUpdates(source, value => {
|
|
88
|
+
try {
|
|
89
|
+
input!.value = parser.format(value) as ParserInput<P>;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (!(error instanceof ParserFormatError)) {
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
watchUpdates(input, value => {
|
|
98
|
+
const result = parser.parse(value);
|
|
99
|
+
if (result.type === "valid") {
|
|
100
|
+
source.value = result.value;
|
|
101
|
+
invalid.value = undefined;
|
|
102
|
+
} else {
|
|
103
|
+
invalid.value = result.value as ParserMessages<P>;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const validator = Validator.attach(source);
|
|
108
|
+
validator.attach(input);
|
|
109
|
+
|
|
110
|
+
for (const key in parser.messages) {
|
|
111
|
+
validator.prependRule({
|
|
112
|
+
validate: () => invalid.value !== key,
|
|
113
|
+
message: parser.messages[key],
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return input;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface IntParserOptions {
|
|
121
|
+
/**
|
|
122
|
+
* A function to check if the parsed value is in a valid range.
|
|
123
|
+
*
|
|
124
|
+
* By default, the value must be a safe integer.
|
|
125
|
+
*/
|
|
126
|
+
testRange?: (value: number) => boolean;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A validation message if the format is invalid.
|
|
130
|
+
*/
|
|
131
|
+
format: unknown;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A validation message if the parsed value is out of range.
|
|
135
|
+
*
|
|
136
|
+
* By default, the format validation message is used.
|
|
137
|
+
*/
|
|
138
|
+
range?: unknown;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a parser for integers for use with {@link parse}.
|
|
143
|
+
*/
|
|
144
|
+
export function intParser(options: IntParserOptions): Parser<number, string, "format" | "range"> {
|
|
145
|
+
const range = options.testRange ?? Number.isSafeInteger;
|
|
146
|
+
return {
|
|
147
|
+
parse(input) {
|
|
148
|
+
if (!/^-?\d+$/.test(input)) {
|
|
149
|
+
return { type: "invalid", value: "format" };
|
|
150
|
+
}
|
|
151
|
+
const value = Number(input);
|
|
152
|
+
if (!range(value)) {
|
|
153
|
+
return { type: "invalid", value: "range" };
|
|
154
|
+
}
|
|
155
|
+
return { type: "valid", value };
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
format(source) {
|
|
159
|
+
return String(source);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
messages: {
|
|
163
|
+
format: options.format,
|
|
164
|
+
range: options.range ?? options.format,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { extract } from "rvx";
|
|
2
|
+
|
|
3
|
+
import { THEME, Theme } from "./theme.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get a class name from the current theme.
|
|
7
|
+
*
|
|
8
|
+
* @throws An error if the current theme doesn't support the specified key.
|
|
9
|
+
*/
|
|
10
|
+
export function themeClass(key: keyof Theme): string {
|
|
11
|
+
const theme = extract(THEME);
|
|
12
|
+
if (!theme) {
|
|
13
|
+
throw new Error("theme is not available in the current context");
|
|
14
|
+
}
|
|
15
|
+
const value = theme[key];
|
|
16
|
+
if (value === undefined) {
|
|
17
|
+
throw new Error(`${key} is not supported by the current theme`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { ContextKey } from "rvx";
|
|
2
|
+
|
|
3
|
+
export const THEME = Symbol.for("rvx-ui:theme") as ContextKey<Theme>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A collection of class names that is used as the theme.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { mount, Inject } from "rvx";
|
|
11
|
+
* import { THEME, Button } from "@rvx/ui";
|
|
12
|
+
*
|
|
13
|
+
* import theme from "./theme.module.css";
|
|
14
|
+
*
|
|
15
|
+
* mount(
|
|
16
|
+
* document.body,
|
|
17
|
+
* <Inject key={THEME} value={theme}>
|
|
18
|
+
* {() => <>
|
|
19
|
+
* <Button>Click me!</Button>
|
|
20
|
+
* </>}
|
|
21
|
+
* </Inject>
|
|
22
|
+
* );
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export interface Theme {
|
|
26
|
+
/** Class for all buttons */
|
|
27
|
+
button?: string;
|
|
28
|
+
/** Additional class for buttons with the "default" variant */
|
|
29
|
+
button_default?: string;
|
|
30
|
+
/** Additional class for buttons with the "primary" variant */
|
|
31
|
+
button_primary?: string;
|
|
32
|
+
/** Additional class for buttons with the "success" variant */
|
|
33
|
+
button_success?: string;
|
|
34
|
+
/** Additional class for buttons with the "danger" variant */
|
|
35
|
+
button_danger?: string;
|
|
36
|
+
/** Additional class for buttons with the "warning" variant */
|
|
37
|
+
button_warning?: string;
|
|
38
|
+
/** Additional class for buttons with the "input" variant */
|
|
39
|
+
button_input?: string;
|
|
40
|
+
|
|
41
|
+
/** Class for all checkbox containing labels */
|
|
42
|
+
checkbox_label?: string;
|
|
43
|
+
/** Class for all checkbox inputs */
|
|
44
|
+
checkbox_input?: string;
|
|
45
|
+
/** Class for checkbox text content. This element may also be a text block. */
|
|
46
|
+
checkbox_content?: string;
|
|
47
|
+
|
|
48
|
+
/** Class for collapses */
|
|
49
|
+
collapse?: string;
|
|
50
|
+
/** Class for collapses that is when it's size has been measured. */
|
|
51
|
+
collapse_sized?: string;
|
|
52
|
+
/** Class for an additional element between the collapse container and the content. If this is undefined, the element is omitted. */
|
|
53
|
+
collapse_view?: string;
|
|
54
|
+
/** Class that is added to collapses to play an alert animation. */
|
|
55
|
+
collapse_alert?: string;
|
|
56
|
+
/** Additional class for visible collapses */
|
|
57
|
+
collapse_visible?: string;
|
|
58
|
+
/** Class for collapse content */
|
|
59
|
+
collapse_content?: string;
|
|
60
|
+
|
|
61
|
+
/** Class for all columns */
|
|
62
|
+
column?: string;
|
|
63
|
+
/** Additional class for columns of content */
|
|
64
|
+
column_content?: string;
|
|
65
|
+
/** Additional class for columns of controls */
|
|
66
|
+
column_control?: string;
|
|
67
|
+
|
|
68
|
+
/** Class for all inline control groups */
|
|
69
|
+
control_group?: string;
|
|
70
|
+
|
|
71
|
+
/** Class for all dialog containers */
|
|
72
|
+
dialog_container?: string;
|
|
73
|
+
/** Class for all dialog bodies */
|
|
74
|
+
dialog_body?: string;
|
|
75
|
+
/** Class for all dialog footers */
|
|
76
|
+
dialog_footer?: string;
|
|
77
|
+
|
|
78
|
+
/** Class for all dropdown roots. */
|
|
79
|
+
dropdown?: string;
|
|
80
|
+
/** Additional class for nested dropdowns. */
|
|
81
|
+
dropdown_expansion?: string;
|
|
82
|
+
/** Class for the dropdown scroll area. */
|
|
83
|
+
dropdown_scroll_area?: string;
|
|
84
|
+
/** Class for the dropdown content. */
|
|
85
|
+
dropdown_content?: string;
|
|
86
|
+
/** Class for all dropdown items. */
|
|
87
|
+
dropdown_item?: string;
|
|
88
|
+
/** Class for the currently active dropdown item. */
|
|
89
|
+
dropdown_item_active?: string;
|
|
90
|
+
|
|
91
|
+
/** Class for growing flex space */
|
|
92
|
+
flex_space?: string;
|
|
93
|
+
|
|
94
|
+
/** Class for all headings which are also semantic elements like h1, h2 etc. */
|
|
95
|
+
heading?: string;
|
|
96
|
+
|
|
97
|
+
/** Class for all labels */
|
|
98
|
+
label?: string;
|
|
99
|
+
|
|
100
|
+
/** Class for all links */
|
|
101
|
+
link?: string;
|
|
102
|
+
|
|
103
|
+
/** Class for all page roots. */
|
|
104
|
+
page?: string;
|
|
105
|
+
/** Class for page scrollbar size compensation elements. */
|
|
106
|
+
page_scrollbar_comp?: string;
|
|
107
|
+
/** Class for the page content column besides the scrollbar size compensation element. */
|
|
108
|
+
page_content_col?: string;
|
|
109
|
+
/** Class for page content roots. */
|
|
110
|
+
page_content?: string;
|
|
111
|
+
|
|
112
|
+
/** Class for all popover roots. */
|
|
113
|
+
popover?: string;
|
|
114
|
+
/** Class for the spike container area. */
|
|
115
|
+
popover_spike_area?: string;
|
|
116
|
+
/** Class for the popover scrollable area. */
|
|
117
|
+
popover_scroll_area?: string;
|
|
118
|
+
/** Class for the spike root. */
|
|
119
|
+
popover_spike?: string;
|
|
120
|
+
/** Class for the popover content. */
|
|
121
|
+
popover_content?: string;
|
|
122
|
+
|
|
123
|
+
/** Class for all radio button columns */
|
|
124
|
+
radio_buttons?: string;
|
|
125
|
+
/** Class for all radio button containing labels */
|
|
126
|
+
radio_button_label?: string;
|
|
127
|
+
/** Class for all radio button inputs */
|
|
128
|
+
radio_button_input?: string;
|
|
129
|
+
/** Class for radio button text content. This element may also be a text block. */
|
|
130
|
+
radio_button_content?: string;
|
|
131
|
+
|
|
132
|
+
/** Class for all rows */
|
|
133
|
+
row?: string;
|
|
134
|
+
/** Additional class for rows of content */
|
|
135
|
+
row_content?: string;
|
|
136
|
+
/** Additional class for rows of controls */
|
|
137
|
+
row_control?: string;
|
|
138
|
+
|
|
139
|
+
/** Class for all scroll view roots */
|
|
140
|
+
scroll_view?: string;
|
|
141
|
+
/** Class for the scrolling area. */
|
|
142
|
+
scroll_view_area?: string;
|
|
143
|
+
/** Class for scroll view content root. */
|
|
144
|
+
scroll_view_content?: string;
|
|
145
|
+
/** Class for scroll view start overflow indicators. */
|
|
146
|
+
scroll_view_indicator_start?: string;
|
|
147
|
+
/** Class for scroll view end overflow indicators. */
|
|
148
|
+
scroll_view_indicator_end?: string;
|
|
149
|
+
/** Class for visible overflow indicators. */
|
|
150
|
+
scroll_view_indicator_visible?: string;
|
|
151
|
+
|
|
152
|
+
/** Class for all text inputs */
|
|
153
|
+
text_input?: string;
|
|
154
|
+
|
|
155
|
+
/** Class for all text blocks */
|
|
156
|
+
text?: string;
|
|
157
|
+
|
|
158
|
+
/** Class for all validation messages */
|
|
159
|
+
validation_message?: string;
|
|
160
|
+
/** Class for all validation message containers. */
|
|
161
|
+
validation_message_container?: string;
|
|
162
|
+
|
|
163
|
+
/** Class for all text values */
|
|
164
|
+
value?: string;
|
|
165
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { sig, Signal, watchUpdates } from "rvx";
|
|
2
|
+
|
|
3
|
+
import { validatorFor } from "../components/validation.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a signal that trims input.
|
|
7
|
+
*
|
|
8
|
+
* This supports validation.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <TextInput value={someSignal.pipe(trim)} />;
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function trim(source: Signal<string>): Signal<string> {
|
|
16
|
+
const input = sig(source.value);
|
|
17
|
+
|
|
18
|
+
watchUpdates(input, value => {
|
|
19
|
+
source.value = value.trim();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
watchUpdates(source, value => {
|
|
23
|
+
if (value !== input.value.trim()) {
|
|
24
|
+
input.value = value;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
validatorFor(source)?.attach(input);
|
|
29
|
+
return input;
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Expression, map } from "rvx";
|
|
2
|
+
|
|
3
|
+
export type SizeContext = "content" | "control";
|
|
4
|
+
|
|
5
|
+
export type AriaLive = "off" | "polite" | "assertive";
|
|
6
|
+
export type AriaRelevant = "additions" | "removals" | "text" | "all" | "additions removals" | "additions text" | "removals text";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Utility to map expressions of strings or string arrays to (e.g. space) separated strings.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <a rel={separated(["noreferrer", "author"], " ")} />;
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function separated(input: Expression<string | string[] | undefined>, sep: string): Expression<string | undefined> {
|
|
17
|
+
return map(input, v => {
|
|
18
|
+
if (Array.isArray(v)) {
|
|
19
|
+
return v.join(sep);
|
|
20
|
+
}
|
|
21
|
+
return v;
|
|
22
|
+
});
|
|
23
|
+
}
|