tosijs-ui 1.0.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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/ab-test.d.ts +14 -0
- package/dist/ab-test.js +116 -0
- package/dist/babylon-3d.d.ts +53 -0
- package/dist/babylon-3d.js +292 -0
- package/dist/bodymovin-player.d.ts +32 -0
- package/dist/bodymovin-player.js +172 -0
- package/dist/bp-loader.d.ts +1 -0
- package/dist/bp-loader.js +26 -0
- package/dist/carousel.d.ts +113 -0
- package/dist/carousel.js +308 -0
- package/dist/code-editor.d.ts +27 -0
- package/dist/code-editor.js +102 -0
- package/dist/color-input.d.ts +41 -0
- package/dist/color-input.js +112 -0
- package/dist/data-table.d.ts +79 -0
- package/dist/data-table.js +774 -0
- package/dist/drag-and-drop.d.ts +2 -0
- package/dist/drag-and-drop.js +386 -0
- package/dist/editable-rect.d.ts +97 -0
- package/dist/editable-rect.js +450 -0
- package/dist/filter-builder.d.ts +64 -0
- package/dist/filter-builder.js +468 -0
- package/dist/float.d.ts +18 -0
- package/dist/float.js +170 -0
- package/dist/form.d.ts +68 -0
- package/dist/form.js +466 -0
- package/dist/gamepad.d.ts +34 -0
- package/dist/gamepad.js +115 -0
- package/dist/icon-data.d.ts +312 -0
- package/dist/icon-data.js +308 -0
- package/dist/icon-types.d.ts +7 -0
- package/dist/icon-types.js +1 -0
- package/dist/icons.d.ts +17 -0
- package/dist/icons.js +374 -0
- package/dist/iife.js +69 -0
- package/dist/iife.js.map +49 -0
- package/dist/index-iife.d.ts +1 -0
- package/dist/index-iife.js +4 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +47 -0
- package/dist/live-example.d.ts +63 -0
- package/dist/live-example.js +611 -0
- package/dist/localize.d.ts +46 -0
- package/dist/localize.js +381 -0
- package/dist/make-sorter.d.ts +3 -0
- package/dist/make-sorter.js +119 -0
- package/dist/make-sorter.test.d.ts +1 -0
- package/dist/make-sorter.test.js +48 -0
- package/dist/mapbox.d.ts +24 -0
- package/dist/mapbox.js +161 -0
- package/dist/markdown-viewer.d.ts +17 -0
- package/dist/markdown-viewer.js +173 -0
- package/dist/match-shortcut.d.ts +9 -0
- package/dist/match-shortcut.js +13 -0
- package/dist/match-shortcut.test.d.ts +1 -0
- package/dist/match-shortcut.test.js +194 -0
- package/dist/menu.d.ts +60 -0
- package/dist/menu.js +614 -0
- package/dist/notifications.d.ts +106 -0
- package/dist/notifications.js +308 -0
- package/dist/password-strength.d.ts +35 -0
- package/dist/password-strength.js +302 -0
- package/dist/playwright.config.d.ts +9 -0
- package/dist/playwright.config.js +73 -0
- package/dist/pop-float.d.ts +10 -0
- package/dist/pop-float.js +231 -0
- package/dist/rating.d.ts +62 -0
- package/dist/rating.js +192 -0
- package/dist/rich-text.d.ts +35 -0
- package/dist/rich-text.js +296 -0
- package/dist/segmented.d.ts +80 -0
- package/dist/segmented.js +298 -0
- package/dist/select.d.ts +43 -0
- package/dist/select.js +427 -0
- package/dist/side-nav.d.ts +36 -0
- package/dist/side-nav.js +106 -0
- package/dist/size-break.d.ts +18 -0
- package/dist/size-break.js +118 -0
- package/dist/sizer.d.ts +34 -0
- package/dist/sizer.js +92 -0
- package/dist/src/ab-test.d.ts +14 -0
- package/dist/src/babylon-3d.d.ts +53 -0
- package/dist/src/bodymovin-player.d.ts +32 -0
- package/dist/src/bp-loader.d.ts +0 -0
- package/dist/src/carousel.d.ts +113 -0
- package/dist/src/code-editor.d.ts +27 -0
- package/dist/src/color-input.d.ts +41 -0
- package/dist/src/data-table.d.ts +79 -0
- package/dist/src/drag-and-drop.d.ts +2 -0
- package/dist/src/editable-rect.d.ts +97 -0
- package/dist/src/filter-builder.d.ts +64 -0
- package/dist/src/float.d.ts +18 -0
- package/dist/src/form.d.ts +68 -0
- package/dist/src/gamepad.d.ts +34 -0
- package/dist/src/icon-data.d.ts +309 -0
- package/dist/src/icon-types.d.ts +7 -0
- package/dist/src/icons.d.ts +17 -0
- package/dist/src/index.d.ts +37 -0
- package/dist/src/live-example.d.ts +51 -0
- package/dist/src/localize.d.ts +30 -0
- package/dist/src/make-sorter.d.ts +3 -0
- package/dist/src/mapbox.d.ts +24 -0
- package/dist/src/markdown-viewer.d.ts +15 -0
- package/dist/src/match-shortcut.d.ts +9 -0
- package/dist/src/menu.d.ts +60 -0
- package/dist/src/notifications.d.ts +106 -0
- package/dist/src/password-strength.d.ts +35 -0
- package/dist/src/pop-float.d.ts +10 -0
- package/dist/src/rating.d.ts +62 -0
- package/dist/src/rich-text.d.ts +28 -0
- package/dist/src/segmented.d.ts +80 -0
- package/dist/src/select.d.ts +43 -0
- package/dist/src/side-nav.d.ts +36 -0
- package/dist/src/size-break.d.ts +18 -0
- package/dist/src/sizer.d.ts +34 -0
- package/dist/src/tab-selector.d.ts +91 -0
- package/dist/src/tag-list.d.ts +37 -0
- package/dist/src/track-drag.d.ts +5 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/via-tag.d.ts +2 -0
- package/dist/tab-selector.d.ts +91 -0
- package/dist/tab-selector.js +326 -0
- package/dist/tag-list.d.ts +37 -0
- package/dist/tag-list.js +375 -0
- package/dist/track-drag.d.ts +5 -0
- package/dist/track-drag.js +143 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/dist/via-tag.d.ts +2 -0
- package/dist/via-tag.js +102 -0
- package/package.json +58 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Read environment variables from file.
|
|
4
|
+
* https://github.com/motdotla/dotenv
|
|
5
|
+
*/
|
|
6
|
+
// import dotenv from 'dotenv';
|
|
7
|
+
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
|
8
|
+
/**
|
|
9
|
+
* See https://playwright.dev/docs/test-configuration.
|
|
10
|
+
*/
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
testDir: './tests',
|
|
13
|
+
/* Run tests in files in parallel */
|
|
14
|
+
fullyParallel: true,
|
|
15
|
+
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
16
|
+
forbidOnly: !!process.env.CI,
|
|
17
|
+
/* Retry on CI only */
|
|
18
|
+
retries: process.env.CI ? 2 : 0,
|
|
19
|
+
/* Opt out of parallel tests on CI. */
|
|
20
|
+
workers: process.env.CI ? 1 : undefined,
|
|
21
|
+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
22
|
+
reporter: 'html',
|
|
23
|
+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
24
|
+
/* use .pw.ts for playwright (vs. bun) tests */
|
|
25
|
+
testMatch: /.*\.pw\.ts/,
|
|
26
|
+
use: {
|
|
27
|
+
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
28
|
+
// baseURL: 'http://127.0.0.1:3000',
|
|
29
|
+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
30
|
+
trace: 'on-first-retry',
|
|
31
|
+
/* allow https on localhost with self-signed certificate */
|
|
32
|
+
ignoreHTTPSErrors: true,
|
|
33
|
+
},
|
|
34
|
+
/* Configure projects for major browsers */
|
|
35
|
+
projects: [
|
|
36
|
+
{
|
|
37
|
+
name: 'chromium',
|
|
38
|
+
use: { ...devices['Desktop Chrome'] },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'firefox',
|
|
42
|
+
use: { ...devices['Desktop Firefox'] },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'webkit',
|
|
46
|
+
use: { ...devices['Desktop Safari'] },
|
|
47
|
+
},
|
|
48
|
+
/* Test against mobile viewports. */
|
|
49
|
+
// {
|
|
50
|
+
// name: 'Mobile Chrome',
|
|
51
|
+
// use: { ...devices['Pixel 5'] },
|
|
52
|
+
// },
|
|
53
|
+
// {
|
|
54
|
+
// name: 'Mobile Safari',
|
|
55
|
+
// use: { ...devices['iPhone 12'] },
|
|
56
|
+
// },
|
|
57
|
+
/* Test against branded browsers. */
|
|
58
|
+
// {
|
|
59
|
+
// name: 'Microsoft Edge',
|
|
60
|
+
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
|
61
|
+
// },
|
|
62
|
+
// {
|
|
63
|
+
// name: 'Google Chrome',
|
|
64
|
+
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
|
65
|
+
// },
|
|
66
|
+
],
|
|
67
|
+
/* Run your local dev server before starting the tests */
|
|
68
|
+
// webServer: {
|
|
69
|
+
// command: 'npm run start',
|
|
70
|
+
// url: 'http://127.0.0.1:3000',
|
|
71
|
+
// reuseExistingServer: !process.env.CI,
|
|
72
|
+
// },
|
|
73
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ElementPart } from 'tosijs';
|
|
2
|
+
import { XinFloat } from './float';
|
|
3
|
+
export type FloatPosition = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw' | 'en' | 'wn' | 'es' | 'ws' | 'side' | 'auto';
|
|
4
|
+
export interface PopFloatOptions {
|
|
5
|
+
content: HTMLElement | ElementPart[];
|
|
6
|
+
target: HTMLElement;
|
|
7
|
+
position?: FloatPosition;
|
|
8
|
+
}
|
|
9
|
+
export declare const popFloat: (options: PopFloatOptions) => XinFloat;
|
|
10
|
+
export declare const positionFloat: (element: HTMLElement, target: HTMLElement, position?: FloatPosition) => void;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/*#
|
|
2
|
+
# popFloat
|
|
3
|
+
|
|
4
|
+
There are so many cases in user-interfaces where it's useful to pop-up a floating
|
|
5
|
+
user interface element that a simple and reliable way of doing this seems like
|
|
6
|
+
a good idea.
|
|
7
|
+
|
|
8
|
+
The problem with many such approaches is that they assume a highly specific
|
|
9
|
+
use-case (e.g. popup menu or combo box) and while meeting the creator's intended
|
|
10
|
+
purpose admirably, turn out to have some annoying limitation that prevents them
|
|
11
|
+
handling the specific case at hand.
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const { popFloat, positionFloat } = xinjsui
|
|
15
|
+
const { button } = xinjs.elements
|
|
16
|
+
const grid = preview.querySelector('.grid')
|
|
17
|
+
|
|
18
|
+
grid.addEventListener('click', (event) => {
|
|
19
|
+
const { target } = event
|
|
20
|
+
if (!target.closest('button')) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
const float = preview.querySelector('xin-float')
|
|
24
|
+
if (float === null) {
|
|
25
|
+
// create and position a float
|
|
26
|
+
preview.append(
|
|
27
|
+
popFloat({
|
|
28
|
+
content: [
|
|
29
|
+
'hello, I am a float',
|
|
30
|
+
button('close me', {
|
|
31
|
+
onClick(event){
|
|
32
|
+
event.target.closest('xin-float').remove()
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
],
|
|
36
|
+
target,
|
|
37
|
+
position: target.dataset.float
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
} else {
|
|
41
|
+
// position an existing float
|
|
42
|
+
positionFloat(float, target, target.dataset.float)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
```html
|
|
47
|
+
<h2>Pop Float Demo</h2>
|
|
48
|
+
<div class="grid">
|
|
49
|
+
<button data-float="nw">nw</button>
|
|
50
|
+
<button data-float="n">n</button>
|
|
51
|
+
<button data-float="ne">ne</button>
|
|
52
|
+
<button data-float="wn">wn</button>
|
|
53
|
+
<span> </span>
|
|
54
|
+
<button data-float="en">en</button>
|
|
55
|
+
<button data-float="w">w</button>
|
|
56
|
+
<button data-float="auto">auto</button>
|
|
57
|
+
<button data-float="e">e</button>
|
|
58
|
+
<button data-float="ws">ws</button>
|
|
59
|
+
<button data-float="side">side</button>
|
|
60
|
+
<button data-float="es">es</button>
|
|
61
|
+
<button data-float="sw">sw</button>
|
|
62
|
+
<button data-float="s">s</button>
|
|
63
|
+
<button data-float="se">se</button>
|
|
64
|
+
</div>
|
|
65
|
+
```
|
|
66
|
+
```css
|
|
67
|
+
.preview .grid {
|
|
68
|
+
display: grid;
|
|
69
|
+
grid-template-columns: 80px 80px 80px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.preview xin-float {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
border-radius: 5px;
|
|
76
|
+
padding: 10px;
|
|
77
|
+
background: white;
|
|
78
|
+
box-shadow: 2px 10px 5px #0004;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## popFloat
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
export interface PopFloatOptions {
|
|
86
|
+
content: HTMLElement | ElementPart[]
|
|
87
|
+
target: HTMLElement
|
|
88
|
+
position?: FloatPosition
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const popFloat = (options: PopFloatOptions): XinFloat
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Create a `<xin-float>` with the content provided, positioned as specified (or automatically).
|
|
95
|
+
|
|
96
|
+
## positionFloat
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
export const positionFloat = (
|
|
100
|
+
element: HTMLElement,
|
|
101
|
+
target: HTMLElement,
|
|
102
|
+
position?: FloatPosition
|
|
103
|
+
remainOnScroll?: 'hide' | 'remove' | boolean // default is 'remove'
|
|
104
|
+
remainOnResize?: 'hide' | 'remove' | boolean // default is 'remove'
|
|
105
|
+
): void
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This allows you to, for example, provide a global menu that can be used on any element
|
|
109
|
+
instead of needing to have a whole instance of the menu wrapped around every instance
|
|
110
|
+
of the thing you want the menu to affect (a common anti-pattern of front-end frameworks).
|
|
111
|
+
|
|
112
|
+
### Handling Overflow
|
|
113
|
+
|
|
114
|
+
`positionFloat` automatically sets two css-variables `--max-height` and `--max-width` on
|
|
115
|
+
the floating element to help you deal with overflow (e.g. in menus). E.g. if the float
|
|
116
|
+
is positioned with `top: 125px` then it will set `--max-height: calc(100vh - 125px)`.
|
|
117
|
+
|
|
118
|
+
## FloatPosition
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
export type FloatPosition =
|
|
122
|
+
| 'n'
|
|
123
|
+
| 's'
|
|
124
|
+
| 'e'
|
|
125
|
+
| 'w'
|
|
126
|
+
| 'ne'
|
|
127
|
+
| 'nw'
|
|
128
|
+
| 'se'
|
|
129
|
+
| 'sw'
|
|
130
|
+
| 'en'
|
|
131
|
+
| 'wn'
|
|
132
|
+
| 'es'
|
|
133
|
+
| 'ws'
|
|
134
|
+
| 'side'
|
|
135
|
+
| 'auto'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
*/
|
|
139
|
+
import { xinFloat } from './float';
|
|
140
|
+
import { bringToFront } from './track-drag';
|
|
141
|
+
export const popFloat = (options) => {
|
|
142
|
+
const { content, target, position } = options;
|
|
143
|
+
const float = Array.isArray(content)
|
|
144
|
+
? xinFloat(...content)
|
|
145
|
+
: xinFloat(content);
|
|
146
|
+
positionFloat(float, target, position);
|
|
147
|
+
document.body.append(float);
|
|
148
|
+
return float;
|
|
149
|
+
};
|
|
150
|
+
export const positionFloat = (element, target, position) => {
|
|
151
|
+
{
|
|
152
|
+
const { position } = getComputedStyle(element);
|
|
153
|
+
if (position !== 'fixed') {
|
|
154
|
+
element.style.position = 'fixed';
|
|
155
|
+
}
|
|
156
|
+
bringToFront(element);
|
|
157
|
+
}
|
|
158
|
+
const { left, top, width, height } = target.getBoundingClientRect();
|
|
159
|
+
const cx = left + width * 0.5;
|
|
160
|
+
const cy = top + height * 0.5;
|
|
161
|
+
const w = window.innerWidth;
|
|
162
|
+
const h = window.innerHeight;
|
|
163
|
+
if (position === 'side') {
|
|
164
|
+
position = ((cx < w * 0.5 ? 'e' : 'w') +
|
|
165
|
+
(cy < h * 0.5 ? 's' : 'n'));
|
|
166
|
+
}
|
|
167
|
+
else if (position === 'auto' || position === undefined) {
|
|
168
|
+
position = ((cy < h * 0.5 ? 's' : 'n') +
|
|
169
|
+
(cx < w * 0.5 ? 'e' : 'w'));
|
|
170
|
+
}
|
|
171
|
+
element.style.top =
|
|
172
|
+
element.style.left =
|
|
173
|
+
element.style.right =
|
|
174
|
+
element.style.bottom =
|
|
175
|
+
element.style.transform =
|
|
176
|
+
'';
|
|
177
|
+
if (position.length === 2) {
|
|
178
|
+
const [first, second] = position;
|
|
179
|
+
switch (first) {
|
|
180
|
+
case 'n':
|
|
181
|
+
element.style.bottom = (h - top).toFixed(2) + 'px';
|
|
182
|
+
break;
|
|
183
|
+
case 'e':
|
|
184
|
+
element.style.left = (left + width).toFixed(2) + 'px';
|
|
185
|
+
break;
|
|
186
|
+
case 's':
|
|
187
|
+
element.style.top = (top + height).toFixed(2) + 'px';
|
|
188
|
+
break;
|
|
189
|
+
case 'w':
|
|
190
|
+
element.style.right = (w - left).toFixed(2) + 'px';
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
switch (second) {
|
|
194
|
+
case 'n':
|
|
195
|
+
element.style.bottom = (h - top - height).toFixed(2) + 'px';
|
|
196
|
+
break;
|
|
197
|
+
case 'e':
|
|
198
|
+
element.style.left = left.toFixed(2) + 'px';
|
|
199
|
+
break;
|
|
200
|
+
case 's':
|
|
201
|
+
element.style.top = top.toFixed(2) + 'px';
|
|
202
|
+
break;
|
|
203
|
+
case 'w':
|
|
204
|
+
element.style.right = (w - left - width).toFixed(2) + 'px';
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
element.style.transform = '';
|
|
208
|
+
}
|
|
209
|
+
else if (position === 'n') {
|
|
210
|
+
element.style.bottom = (h - top).toFixed(2) + 'px';
|
|
211
|
+
element.style.left = cx.toFixed(2) + 'px';
|
|
212
|
+
element.style.transform = 'translateX(-50%)';
|
|
213
|
+
}
|
|
214
|
+
else if (position === 's') {
|
|
215
|
+
element.style.top = (top + height).toFixed(2) + 'px';
|
|
216
|
+
element.style.left = cx.toFixed(2) + 'px';
|
|
217
|
+
element.style.transform = 'translateX(-50%)';
|
|
218
|
+
}
|
|
219
|
+
else if (position === 'e') {
|
|
220
|
+
element.style.left = (left + width).toFixed(2) + 'px';
|
|
221
|
+
element.style.top = cy.toFixed(2) + 'px';
|
|
222
|
+
element.style.transform = 'translateY(-50%)';
|
|
223
|
+
}
|
|
224
|
+
else if (position === 'w') {
|
|
225
|
+
element.style.right = (w - left).toFixed(2) + 'px';
|
|
226
|
+
element.style.top = cy.toFixed(2) + 'px';
|
|
227
|
+
element.style.transform = 'translateY(-50%)';
|
|
228
|
+
}
|
|
229
|
+
element.style.setProperty('--max-height', `calc(100vh - ${element.style.top || element.style.bottom})`);
|
|
230
|
+
element.style.setProperty('--max-width', `calc(100vw - ${element.style.left || element.style.right})`);
|
|
231
|
+
};
|
package/dist/rating.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Component, ElementCreator } from 'tosijs';
|
|
2
|
+
export declare class XinRating extends Component {
|
|
3
|
+
iconSize: number;
|
|
4
|
+
min: 0 | 1;
|
|
5
|
+
max: number;
|
|
6
|
+
step: number;
|
|
7
|
+
value: number | null;
|
|
8
|
+
icon: string;
|
|
9
|
+
ratingFill: string;
|
|
10
|
+
ratingStroke: string;
|
|
11
|
+
emptyFill: string;
|
|
12
|
+
emptyStroke: string;
|
|
13
|
+
readonly: boolean;
|
|
14
|
+
hollow: boolean;
|
|
15
|
+
static styleSpec: {
|
|
16
|
+
':host': {
|
|
17
|
+
display: string;
|
|
18
|
+
position: string;
|
|
19
|
+
width: string;
|
|
20
|
+
};
|
|
21
|
+
':host::part(container)': {
|
|
22
|
+
position: string;
|
|
23
|
+
display: string;
|
|
24
|
+
};
|
|
25
|
+
':host::part(empty), :host::part(filled)': {
|
|
26
|
+
height: string;
|
|
27
|
+
whiteSpace: string;
|
|
28
|
+
overflow: string;
|
|
29
|
+
};
|
|
30
|
+
':host::part(empty)': {
|
|
31
|
+
pointerEvents: string;
|
|
32
|
+
_xinIconFill: string;
|
|
33
|
+
_xinIconStroke: string;
|
|
34
|
+
};
|
|
35
|
+
':host::part(filled)': {
|
|
36
|
+
position: string;
|
|
37
|
+
left: number;
|
|
38
|
+
_xinIconFill: string;
|
|
39
|
+
_xinIconStroke: string;
|
|
40
|
+
};
|
|
41
|
+
':host svg': {
|
|
42
|
+
transform: string;
|
|
43
|
+
pointerEvents: string;
|
|
44
|
+
transition: string;
|
|
45
|
+
};
|
|
46
|
+
':host svg:hover': {
|
|
47
|
+
transform: string;
|
|
48
|
+
};
|
|
49
|
+
':host svg:active': {
|
|
50
|
+
transform: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
constructor();
|
|
54
|
+
content: () => HTMLSpanElement;
|
|
55
|
+
displayValue(value: number | null): void;
|
|
56
|
+
update: (event: Event) => void;
|
|
57
|
+
handleKey: (event: KeyboardEvent) => void;
|
|
58
|
+
connectedCallback(): void;
|
|
59
|
+
private _renderedIcon;
|
|
60
|
+
render(): void;
|
|
61
|
+
}
|
|
62
|
+
export declare const xinRating: ElementCreator<XinRating>;
|
package/dist/rating.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/*#
|
|
2
|
+
# rating
|
|
3
|
+
|
|
4
|
+
`XinRating` / `<xin-rating>` provides a drop-in replacement for an `<input>`
|
|
5
|
+
that renders a rating using <xin-icon icon="star" color="red"></xin-icon>s.
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<xin-rating value=3.4></xin-rating>
|
|
9
|
+
<xin-rating min=0 value=3.4 step=0.5 hollow></xin-rating>
|
|
10
|
+
<xin-rating value=3.4 color="deepskyblue"></xin-rating>
|
|
11
|
+
<xin-rating value=3.1 max=10 color="hotpink" icon="heart" icon-size=32></xin-rating>
|
|
12
|
+
```
|
|
13
|
+
```css
|
|
14
|
+
.preview {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Attributes
|
|
21
|
+
|
|
22
|
+
- `icon-size` (24 by default) determines the height of the control and along with `max` its width
|
|
23
|
+
- `max` maximum rating
|
|
24
|
+
- `min` (1 by default) can be 0 or 1 (allowing ratings of 0 to max or 1 to max)
|
|
25
|
+
- `step` (0.5 by default) granularity of rating
|
|
26
|
+
- `icon` ('star' by default) determines the icon used
|
|
27
|
+
- `rating-stroke` (#f91 by default) is the stroke of rating icons
|
|
28
|
+
- `rating-fill` (#e81 by default) is the color of rating icons
|
|
29
|
+
- `empty-stroke` (none by default) is the color of background icons
|
|
30
|
+
- `empty-fill` (#ccc by default) is the color of background icons
|
|
31
|
+
- `readonly` (false by default) prevents the user from changing the rating
|
|
32
|
+
- `hollow` (false by default) makes the empty rating icons hollow.
|
|
33
|
+
|
|
34
|
+
## Keyboard
|
|
35
|
+
|
|
36
|
+
`<xin-rating>` should be fully keyboard navigable (and, I hope, accessible).
|
|
37
|
+
|
|
38
|
+
The up key increases the rating, down descreases it. This is the same
|
|
39
|
+
as the behavior of `<input type="number">`, [Shoelace's rating widget](https://shoelace.style/components/rating/),
|
|
40
|
+
and (in my opinion) common sense, but not like [MUI's rating widget](https://mui.com/material-ui/react-rating/).
|
|
41
|
+
*/
|
|
42
|
+
import { Component, elements, vars } from 'xinjs';
|
|
43
|
+
import { icons } from './icons';
|
|
44
|
+
const { span } = elements;
|
|
45
|
+
export class XinRating extends Component {
|
|
46
|
+
iconSize = 24;
|
|
47
|
+
min = 1;
|
|
48
|
+
max = 5;
|
|
49
|
+
step = 1;
|
|
50
|
+
value = null;
|
|
51
|
+
icon = 'star';
|
|
52
|
+
ratingFill = '#f91';
|
|
53
|
+
ratingStroke = '#e81';
|
|
54
|
+
emptyFill = '#ccc';
|
|
55
|
+
emptyStroke = 'none';
|
|
56
|
+
readonly = false;
|
|
57
|
+
hollow = false;
|
|
58
|
+
static styleSpec = {
|
|
59
|
+
':host': {
|
|
60
|
+
display: 'inline-block',
|
|
61
|
+
position: 'relative',
|
|
62
|
+
width: 'fit-content',
|
|
63
|
+
},
|
|
64
|
+
':host::part(container)': {
|
|
65
|
+
position: 'relative',
|
|
66
|
+
display: 'inline-block',
|
|
67
|
+
},
|
|
68
|
+
':host::part(empty), :host::part(filled)': {
|
|
69
|
+
height: '100%',
|
|
70
|
+
whiteSpace: 'nowrap',
|
|
71
|
+
overflow: 'hidden',
|
|
72
|
+
},
|
|
73
|
+
':host::part(empty)': {
|
|
74
|
+
pointerEvents: 'none',
|
|
75
|
+
_xinIconFill: vars.emptyFill,
|
|
76
|
+
_xinIconStroke: vars.emptyStroke,
|
|
77
|
+
},
|
|
78
|
+
':host::part(filled)': {
|
|
79
|
+
position: 'absolute',
|
|
80
|
+
left: 0,
|
|
81
|
+
_xinIconFill: vars.ratingFill,
|
|
82
|
+
_xinIconStroke: vars.ratingStroke,
|
|
83
|
+
},
|
|
84
|
+
':host svg': {
|
|
85
|
+
transform: 'scale(0.9)',
|
|
86
|
+
pointerEvents: 'all !important',
|
|
87
|
+
transition: '0.25s ease-in-out',
|
|
88
|
+
},
|
|
89
|
+
':host svg:hover': {
|
|
90
|
+
transform: 'scale(1)',
|
|
91
|
+
},
|
|
92
|
+
':host svg:active': {
|
|
93
|
+
transform: 'scale(1.1)',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
constructor() {
|
|
97
|
+
super();
|
|
98
|
+
this.initAttributes('max', 'min', 'icon', 'step', 'ratingStroke', 'ratingColor', 'emptyStroke', 'emptyColor', 'readonly', 'iconSize', 'hollow');
|
|
99
|
+
}
|
|
100
|
+
content = () => span({ part: 'container' }, span({ part: 'empty' }), span({ part: 'filled' }));
|
|
101
|
+
displayValue(value) {
|
|
102
|
+
const { empty, filled } = this.parts;
|
|
103
|
+
const roundedValue = Math.round((value || 0) / this.step) * this.step;
|
|
104
|
+
filled.style.width = (roundedValue / this.max) * empty.offsetWidth + 'px';
|
|
105
|
+
}
|
|
106
|
+
update = (event) => {
|
|
107
|
+
if (this.readonly) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const { empty } = this.parts;
|
|
111
|
+
const x = event instanceof MouseEvent
|
|
112
|
+
? event.pageX - empty.getBoundingClientRect().x
|
|
113
|
+
: 0;
|
|
114
|
+
const value = Math.min(Math.max(this.min, Math.round(((x / empty.offsetWidth) * this.max) / this.step + this.step * 0.5) * this.step), this.max);
|
|
115
|
+
if (event.type === 'click') {
|
|
116
|
+
this.value = value;
|
|
117
|
+
}
|
|
118
|
+
else if (event.type === 'mousemove') {
|
|
119
|
+
this.displayValue(value);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
this.displayValue(this.value || 0);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
handleKey = (event) => {
|
|
126
|
+
let value = Number(this.value);
|
|
127
|
+
if (value == null) {
|
|
128
|
+
value = Math.round((this.min + this.max) * 0.5 * this.step) * this.step;
|
|
129
|
+
}
|
|
130
|
+
let blockEvent = false;
|
|
131
|
+
switch (event.key) {
|
|
132
|
+
case 'ArrowUp':
|
|
133
|
+
case 'ArrowRight':
|
|
134
|
+
value += this.step;
|
|
135
|
+
blockEvent = true;
|
|
136
|
+
break;
|
|
137
|
+
case 'ArrowDown':
|
|
138
|
+
case 'ArrowLeft':
|
|
139
|
+
value -= this.step;
|
|
140
|
+
blockEvent = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
this.value = Math.max(Math.min(value, this.max), this.min);
|
|
144
|
+
if (blockEvent) {
|
|
145
|
+
event.stopPropagation();
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
connectedCallback() {
|
|
150
|
+
super.connectedCallback();
|
|
151
|
+
const { container } = this.parts;
|
|
152
|
+
container.tabIndex = 0;
|
|
153
|
+
container.addEventListener('mousemove', this.update, true);
|
|
154
|
+
container.addEventListener('mouseleave', this.update);
|
|
155
|
+
container.addEventListener('blur', this.update);
|
|
156
|
+
container.addEventListener('click', this.update);
|
|
157
|
+
container.addEventListener('keydown', this.handleKey);
|
|
158
|
+
}
|
|
159
|
+
_renderedIcon = '';
|
|
160
|
+
render() {
|
|
161
|
+
super.render();
|
|
162
|
+
const height = this.iconSize + 'px';
|
|
163
|
+
this.style.setProperty('--rating-fill', this.ratingFill);
|
|
164
|
+
this.style.setProperty('--rating-stroke', this.ratingStroke);
|
|
165
|
+
this.style.setProperty('--empty-fill', this.emptyFill);
|
|
166
|
+
this.style.setProperty('--empty-stroke', this.emptyStroke);
|
|
167
|
+
this.style.setProperty('--xin-icon-size', height);
|
|
168
|
+
if (this.readonly) {
|
|
169
|
+
this.role = 'image';
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
this.role = 'slider';
|
|
173
|
+
}
|
|
174
|
+
this.ariaLabel = `rating ${this.value} out of ${this.max}`;
|
|
175
|
+
this.ariaValueMax = String(this.max);
|
|
176
|
+
this.ariaValueMin = String(this.min);
|
|
177
|
+
this.ariaValueNow = this.value === null ? String(-1) : String(this.value);
|
|
178
|
+
const { empty, filled } = this.parts;
|
|
179
|
+
empty.classList.toggle('hollow', this.hollow);
|
|
180
|
+
if (this._renderedIcon !== this.icon) {
|
|
181
|
+
this._renderedIcon = this.icon;
|
|
182
|
+
for (let i = 0; i < this.max; i++) {
|
|
183
|
+
empty.append(icons[this.icon]());
|
|
184
|
+
filled.append(icons[this.icon]());
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
this.displayValue(this.value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export const xinRating = XinRating.elementCreator({
|
|
191
|
+
tag: 'xin-rating',
|
|
192
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Component as WebComponent, ElementCreator, PartsMap } from 'tosijs';
|
|
2
|
+
import { XinSelect } from './select';
|
|
3
|
+
export declare function blockStyle(options?: {
|
|
4
|
+
caption: string;
|
|
5
|
+
tagType: string;
|
|
6
|
+
}[]): XinSelect;
|
|
7
|
+
export declare function spacer(width?: string): HTMLSpanElement;
|
|
8
|
+
export declare function elastic(width?: string): HTMLSpanElement;
|
|
9
|
+
export declare function commandButton(title: string, dataCommand: string, icon: SVGElement): HTMLButtonElement;
|
|
10
|
+
export declare const richTextWidgets: () => HTMLSpanElement[];
|
|
11
|
+
interface EditorParts extends PartsMap {
|
|
12
|
+
toolbar: HTMLElement;
|
|
13
|
+
doc: HTMLElement;
|
|
14
|
+
content: HTMLElement;
|
|
15
|
+
}
|
|
16
|
+
export declare class RichText extends WebComponent<EditorParts> {
|
|
17
|
+
widgets: 'none' | 'minimal' | 'default';
|
|
18
|
+
private isInitialized;
|
|
19
|
+
get value(): string;
|
|
20
|
+
set value(docHtml: string);
|
|
21
|
+
blockElement(elt: Node): Element | undefined;
|
|
22
|
+
get selectedBlocks(): any[];
|
|
23
|
+
get selectedText(): string;
|
|
24
|
+
selectionChange: (event: Event, editor: RichText) => void;
|
|
25
|
+
handleSelectChange: (event: Event) => void;
|
|
26
|
+
handleButtonClick: (event: Event) => void;
|
|
27
|
+
content: any[];
|
|
28
|
+
constructor();
|
|
29
|
+
doCommand(command?: string): void;
|
|
30
|
+
updateBlockStyle(): void;
|
|
31
|
+
connectedCallback(): void;
|
|
32
|
+
render(): void;
|
|
33
|
+
}
|
|
34
|
+
export declare const richText: ElementCreator<RichText>;
|
|
35
|
+
export {};
|