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,46 @@
|
|
|
1
|
+
import { Component } from 'tosijs';
|
|
2
|
+
import { XinSelect } from './select';
|
|
3
|
+
interface TranslationMap {
|
|
4
|
+
[key: string]: string[];
|
|
5
|
+
}
|
|
6
|
+
interface I18nConfig {
|
|
7
|
+
locale: string;
|
|
8
|
+
locales: string[];
|
|
9
|
+
languages: string[];
|
|
10
|
+
emoji: string[];
|
|
11
|
+
stringMap: TranslationMap;
|
|
12
|
+
localeOptions: Array<{
|
|
13
|
+
icon: HTMLElement;
|
|
14
|
+
caption: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export declare const i18n: I18nConfig;
|
|
19
|
+
export declare const setLocale: (language: string) => void;
|
|
20
|
+
export declare const updateLocalized: () => void;
|
|
21
|
+
export declare function initLocalization(localizedStrings: string): void;
|
|
22
|
+
export declare function localize(ref: string): string;
|
|
23
|
+
export declare class LocalePicker extends Component {
|
|
24
|
+
hideCaption: boolean;
|
|
25
|
+
content: () => XinSelect;
|
|
26
|
+
constructor();
|
|
27
|
+
render(): void;
|
|
28
|
+
}
|
|
29
|
+
export declare const localePicker: import("tosijs").ElementCreator<Component<import("tosijs").PartsMap>>;
|
|
30
|
+
interface AbstractLocalized {
|
|
31
|
+
localeChanged: () => void;
|
|
32
|
+
connectedCallback: () => void;
|
|
33
|
+
disconnectedCallback: () => void;
|
|
34
|
+
}
|
|
35
|
+
export declare class XinLocalized extends Component {
|
|
36
|
+
static allInstances: Set<AbstractLocalized>;
|
|
37
|
+
contents: () => any;
|
|
38
|
+
refString: string;
|
|
39
|
+
constructor();
|
|
40
|
+
connectedCallback(): void;
|
|
41
|
+
disconnectedCallback(): void;
|
|
42
|
+
localeChanged(): void;
|
|
43
|
+
render(): void;
|
|
44
|
+
}
|
|
45
|
+
export declare const xinLocalized: import("tosijs").ElementCreator<Component<import("tosijs").PartsMap>>;
|
|
46
|
+
export {};
|
package/dist/localize.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/*#
|
|
2
|
+
# localize
|
|
3
|
+
|
|
4
|
+
`xinjs-ui` provides support for localization via the `localize` method and the `<xin-locale-picker>`
|
|
5
|
+
and `<xin-localized>` custom-elements.
|
|
6
|
+
|
|
7
|
+
> ### Important Note
|
|
8
|
+
> This module deals with the **language** used in the user interface. "locale" is
|
|
9
|
+
> *not the same thing*. The (usually) two-letter codes used designate **language**
|
|
10
|
+
> and **not locale**.
|
|
11
|
+
>
|
|
12
|
+
> E.g. the US *locale* includes things like measurement systems
|
|
13
|
+
> and date format. Most European locales use commas where we use decimal points in the US.
|
|
14
|
+
>
|
|
15
|
+
> Similarly, `ja` is the code for the Japanese **language** while `jp` is the **locale**.
|
|
16
|
+
|
|
17
|
+
## `initLocalization(localizationData: string)`
|
|
18
|
+
|
|
19
|
+
Enables localization from TSV string data.
|
|
20
|
+
|
|
21
|
+
## XinLocalePicker
|
|
22
|
+
|
|
23
|
+
A selector that lets the user pick from among supported languages.
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<h3>Locale Picker</h3>
|
|
27
|
+
<xin-locale-picker></xin-locale-picker>
|
|
28
|
+
|
|
29
|
+
<h3>Locale Picker with <code>hide-captions</code></h3>
|
|
30
|
+
<xin-locale-picker hide-caption></xin-locale-picker>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## `localize()`
|
|
34
|
+
|
|
35
|
+
If you just want to localize a string with code, use `localize(s: string): string`.
|
|
36
|
+
|
|
37
|
+
If the reference string only matches when both are converted to
|
|
38
|
+
lowercase, the output string will also be lowercase.
|
|
39
|
+
|
|
40
|
+
E.g. if you have localized `Cancel` as `Annuler`, then `localize("cancel")
|
|
41
|
+
will output `annuler`.
|
|
42
|
+
|
|
43
|
+
### ellipses
|
|
44
|
+
|
|
45
|
+
If you end a string with an ellipsis, `localize` will ignore the ellipsis,
|
|
46
|
+
localize the string, and then append the ellipsis.
|
|
47
|
+
|
|
48
|
+
## `setLocale(language: string)`
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
const { button, p } = xinjs.elements
|
|
52
|
+
const { setLocale } = xinjsui
|
|
53
|
+
|
|
54
|
+
preview.append(
|
|
55
|
+
p(
|
|
56
|
+
button(
|
|
57
|
+
{
|
|
58
|
+
onClick() {
|
|
59
|
+
setLocale('en-US')
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
'setLocale("en-US")'
|
|
63
|
+
)
|
|
64
|
+
),
|
|
65
|
+
p(
|
|
66
|
+
button(
|
|
67
|
+
{
|
|
68
|
+
onClick() {
|
|
69
|
+
setLocale('fr')
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
'setLocale("fr")'
|
|
73
|
+
)
|
|
74
|
+
),
|
|
75
|
+
p(
|
|
76
|
+
button(
|
|
77
|
+
{
|
|
78
|
+
onClick() {
|
|
79
|
+
setLocale('qq')
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
'setLocale("qq") (see console for error message)'
|
|
83
|
+
)
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If you want to directly set locale, just use `setLocale()`.
|
|
89
|
+
|
|
90
|
+
## XinLocalized
|
|
91
|
+
|
|
92
|
+
A span-replacement that automatically localizes its text content.
|
|
93
|
+
By default the case in the localized data is preserved unless the
|
|
94
|
+
reference text is all lowercase, in which case the localized text
|
|
95
|
+
is also lowercased.
|
|
96
|
+
|
|
97
|
+
While viewing this documentation, all `<xin-localized>` elements should display a **red
|
|
98
|
+
underline**.
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<h3>Localized Widgets</h3>
|
|
102
|
+
<button><xin-localized>Yes</xin-localized></button>
|
|
103
|
+
<button><xin-localized>No</xin-localized></button>
|
|
104
|
+
<button><xin-localized>Open…</xin-localized></button> <i>note the ellipsis</i>
|
|
105
|
+
|
|
106
|
+
<h3>Lowercase is preserved</h3>
|
|
107
|
+
<button><xin-localized>yes</xin-localized></button>
|
|
108
|
+
<button><xin-localized>no</xin-localized></button>
|
|
109
|
+
<button><xin-localized>open…</xin-localized></button>
|
|
110
|
+
|
|
111
|
+
<h3>Localized Attribute</h3>
|
|
112
|
+
<input>
|
|
113
|
+
```
|
|
114
|
+
```css
|
|
115
|
+
xin-localized {
|
|
116
|
+
border-bottom: 2px solid red;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
```js
|
|
120
|
+
const { xinLocalized, localize } = xinjsui
|
|
121
|
+
|
|
122
|
+
preview.append(xinLocalized({
|
|
123
|
+
refString: 'localized placeholder',
|
|
124
|
+
localeChanged() {
|
|
125
|
+
this.previousElementSibling.setAttribute('placeholder', localize(this.refString))
|
|
126
|
+
}
|
|
127
|
+
}))
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`<xin-localized>` has a `refString` attribute (which defaults to its initial `textContent`)
|
|
131
|
+
which is the text that it localizes. You can set it directly.
|
|
132
|
+
|
|
133
|
+
It also has an `localeChanged` method which defaults to setting the content of the element
|
|
134
|
+
to the localized reference string, but which you can override, to (for example) set a property
|
|
135
|
+
or attribute of the parent element.
|
|
136
|
+
|
|
137
|
+
> `<xin-localized>` *can* be used inside the shadowDOM of other custom-elements.
|
|
138
|
+
|
|
139
|
+
## `i18n`
|
|
140
|
+
|
|
141
|
+
All of the data can be bound in the `i18n` proxy (`xin.i18n`), including the currently selected
|
|
142
|
+
locale (which will default to `navigator.language`).
|
|
143
|
+
|
|
144
|
+
You can take a look at `xin.i18n` in the console. `i18n` can be used to access localization
|
|
145
|
+
data directly, and also to determine which locales are available `i18n.locales` and set the
|
|
146
|
+
locale programmatically (e.g. `i18n.locale = 'en'`).
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
if (i18n.locales.includes('fr')) {
|
|
150
|
+
i18n.locale = 'fr'
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Creating Localized String Data
|
|
155
|
+
|
|
156
|
+
You can create your own localization data using any spreadsheet and exporting TSV.
|
|
157
|
+
|
|
158
|
+
E.g. you can automatically create localization data
|
|
159
|
+
using something like my [localized](https://docs.google.com/spreadsheets/d/1L0_4g_dDhVCwVVxLzYbMj_H86xSp9lsRCKj7IS9psso/edit?usp=sharing)
|
|
160
|
+
Google Sheet which leverages `googletranslate` to automatically translate reference strings
|
|
161
|
+
(and which you can manually override as you like).
|
|
162
|
+
|
|
163
|
+
E.g. in this demo I've replaced the incorrect translation of "Finnish"
|
|
164
|
+
(`googletranslate` used the word for Finnish nationality rather than the language).
|
|
165
|
+
|
|
166
|
+
The format of the input data is a table in TSV format, that looks like this:
|
|
167
|
+
|
|
168
|
+
en-US | fr | fi | sv | zh
|
|
169
|
+
------|----|----|----|----
|
|
170
|
+
English (US) | French | Finnish | Swedish | Chinese (Mandarin)
|
|
171
|
+
English (US) | Français | suomi | svenska | 中文(普通话)
|
|
172
|
+
🇺🇸 | 🇫🇷 | 🇫🇮 | 🇸🇪 | 🇨🇳
|
|
173
|
+
Icon | Icône | Kuvake | Ikon | 图标
|
|
174
|
+
Ok | D'accord | Ok | Ok | 好的
|
|
175
|
+
Cancel | Annuler | Peruuttaa | Avboka | 取消
|
|
176
|
+
|
|
177
|
+
- Column 1 is your reference language.
|
|
178
|
+
- Row 1 is [language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes).
|
|
179
|
+
- Row 2 is the name of the language in your reference language.
|
|
180
|
+
- Row 3 is the name of the language in itself (because it's silly to expect people
|
|
181
|
+
to know the name of their language in a language they don't know)
|
|
182
|
+
- Row 4 is the flag emoji for that language (yes, that's problematic, but languages
|
|
183
|
+
do not have flags, per se)
|
|
184
|
+
- Rows 5 and on are user interface strings you want to localize
|
|
185
|
+
|
|
186
|
+
In the spreadsheet provided, each cell contains a formula that translates the term
|
|
187
|
+
in the left-most column from the language in that column to the language in the
|
|
188
|
+
destination column. Once you have an automatic translation, you can hand off the
|
|
189
|
+
sheet to language experts to vet the translations.
|
|
190
|
+
|
|
191
|
+
Finally, create a `tsv` file and then turn that into a Typescript file by wrapping
|
|
192
|
+
the content thus:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
export default `( content of tsv file )`
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
You use this data using `initLocalization()`.
|
|
199
|
+
|
|
200
|
+
## Leveraging XinLocalized Automatic Updates
|
|
201
|
+
|
|
202
|
+
If you want to leverage XinLocalized's automatic updates you simply need to
|
|
203
|
+
implement `updateLocale` and register yourself with `XinLocalized.allInstances`
|
|
204
|
+
(which is a `Set<AbstractLocalized>).
|
|
205
|
+
|
|
206
|
+
Typically, this would look like something like:
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
class MyLocalizedComponent extends Component {
|
|
210
|
+
...
|
|
211
|
+
|
|
212
|
+
// register yourself as a localized component
|
|
213
|
+
connectecCallback() {
|
|
214
|
+
super.connectedCallback()
|
|
215
|
+
|
|
216
|
+
XinLocalized.allInstances.add(this)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// avoid leaking!
|
|
220
|
+
disconnectecCallback() {
|
|
221
|
+
super.connectedCallback()
|
|
222
|
+
|
|
223
|
+
XinLocalized.allInstances.delete(this)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// presumably your render method does the right things
|
|
227
|
+
updateLocale = () => {
|
|
228
|
+
this.queueRender()
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
*/
|
|
233
|
+
import { Component, boxedProxy, elements, bindings, observe } from 'xinjs';
|
|
234
|
+
import { makeSorter } from './make-sorter';
|
|
235
|
+
import { xinSelect, XinSelect } from './select';
|
|
236
|
+
const { span } = elements;
|
|
237
|
+
export const { i18n } = boxedProxy({
|
|
238
|
+
i18n: {
|
|
239
|
+
locale: window.navigator.language,
|
|
240
|
+
locales: [window.navigator.language],
|
|
241
|
+
languages: [window.navigator.language],
|
|
242
|
+
emoji: [''],
|
|
243
|
+
stringMap: {},
|
|
244
|
+
localeOptions: [
|
|
245
|
+
{
|
|
246
|
+
icon: span(),
|
|
247
|
+
caption: window.navigator.language,
|
|
248
|
+
value: window.navigator.language,
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
bindings.localeOptions = {
|
|
254
|
+
toDOM(select, options) {
|
|
255
|
+
if (select instanceof XinSelect) {
|
|
256
|
+
select.options = options;
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
export const setLocale = (language) => {
|
|
261
|
+
if (i18n.locales.includes(language)) {
|
|
262
|
+
i18n.locale = language;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
console.error(`language ${language} is not available`);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
export const updateLocalized = () => {
|
|
269
|
+
const localizeds = Array.from(XinLocalized.allInstances);
|
|
270
|
+
for (const localized of localizeds) {
|
|
271
|
+
localized.localeChanged();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
observe(i18n.locale.xinPath, updateLocalized);
|
|
275
|
+
const captionSort = makeSorter((locale) => [
|
|
276
|
+
locale.caption.toLocaleLowerCase(),
|
|
277
|
+
]);
|
|
278
|
+
export function initLocalization(localizedStrings) {
|
|
279
|
+
const [locales, , languages, emoji, ...strings] = localizedStrings
|
|
280
|
+
.split('\n')
|
|
281
|
+
.map((line) => line.split('\t'));
|
|
282
|
+
if (locales && languages && emoji && strings) {
|
|
283
|
+
i18n.locales = locales;
|
|
284
|
+
i18n.languages = languages;
|
|
285
|
+
i18n.emoji = emoji;
|
|
286
|
+
i18n.stringMap = strings.reduce((map, strings) => {
|
|
287
|
+
map[strings[0].toLocaleLowerCase()] = strings;
|
|
288
|
+
return map;
|
|
289
|
+
}, {});
|
|
290
|
+
i18n.localeOptions = locales
|
|
291
|
+
.map((locale, index) => ({
|
|
292
|
+
icon: span({ title: locales[index] }, emoji[index]),
|
|
293
|
+
caption: languages[index],
|
|
294
|
+
value: locale,
|
|
295
|
+
}))
|
|
296
|
+
.sort(captionSort);
|
|
297
|
+
// if user locale isn't available, find the best match
|
|
298
|
+
if (!i18n.locales.includes(i18n.locale.valueOf())) {
|
|
299
|
+
const language = i18n.locale.substring(0, 2);
|
|
300
|
+
i18n.locale =
|
|
301
|
+
i18n.locales.find((locale) => locale.substring(0, 2) === language) ||
|
|
302
|
+
i18n.locales[0];
|
|
303
|
+
}
|
|
304
|
+
updateLocalized();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
export function localize(ref) {
|
|
308
|
+
if (ref.endsWith('…')) {
|
|
309
|
+
return localize(ref.substring(0, ref.length - 1)) + '…';
|
|
310
|
+
}
|
|
311
|
+
const index = i18n.locales.indexOf(i18n.locale.valueOf());
|
|
312
|
+
if (index > -1) {
|
|
313
|
+
const map = i18n.stringMap[ref.toLocaleLowerCase()];
|
|
314
|
+
const localized = map && map[index];
|
|
315
|
+
if (localized) {
|
|
316
|
+
ref =
|
|
317
|
+
ref.toLocaleLowerCase() === ref
|
|
318
|
+
? localized.toLocaleLowerCase()
|
|
319
|
+
: localized.valueOf();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return ref;
|
|
323
|
+
}
|
|
324
|
+
export class LocalePicker extends Component {
|
|
325
|
+
hideCaption = false;
|
|
326
|
+
content = () => {
|
|
327
|
+
return xinSelect({
|
|
328
|
+
part: 'select',
|
|
329
|
+
showIcon: true,
|
|
330
|
+
title: localize('Language'),
|
|
331
|
+
bindValue: i18n.locale,
|
|
332
|
+
bindLocaleOptions: i18n.localeOptions,
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
constructor() {
|
|
336
|
+
super();
|
|
337
|
+
this.initAttributes('hideCaption');
|
|
338
|
+
}
|
|
339
|
+
render() {
|
|
340
|
+
super.render();
|
|
341
|
+
this.parts.select.toggleAttribute('hide-caption', this.hideCaption);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
export const localePicker = LocalePicker.elementCreator({
|
|
345
|
+
tag: 'xin-locale-picker',
|
|
346
|
+
});
|
|
347
|
+
export class XinLocalized extends Component {
|
|
348
|
+
static allInstances = new Set();
|
|
349
|
+
contents = () => elements.xinSlot();
|
|
350
|
+
refString = '';
|
|
351
|
+
constructor() {
|
|
352
|
+
super();
|
|
353
|
+
this.initAttributes('refString');
|
|
354
|
+
}
|
|
355
|
+
connectedCallback() {
|
|
356
|
+
super.connectedCallback();
|
|
357
|
+
XinLocalized.allInstances.add(this);
|
|
358
|
+
}
|
|
359
|
+
disconnectedCallback() {
|
|
360
|
+
super.disconnectedCallback();
|
|
361
|
+
XinLocalized.allInstances.delete(this);
|
|
362
|
+
}
|
|
363
|
+
localeChanged() {
|
|
364
|
+
if (!this.refString) {
|
|
365
|
+
this.refString = this.textContent || '';
|
|
366
|
+
}
|
|
367
|
+
this.textContent = this.refString ? localize(this.refString) : '';
|
|
368
|
+
}
|
|
369
|
+
render() {
|
|
370
|
+
super.render();
|
|
371
|
+
this.localeChanged();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
export const xinLocalized = XinLocalized.elementCreator({
|
|
375
|
+
tag: 'xin-localized',
|
|
376
|
+
styleSpec: {
|
|
377
|
+
':host': {
|
|
378
|
+
pointerEvents: 'none',
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/*#
|
|
2
|
+
# makeSorter
|
|
3
|
+
|
|
4
|
+
I'm always confusing myself when writing sort functions, so I wrote `makeSorter()`. It's
|
|
5
|
+
insanely simple and just works™. It makes writing an array sort callback for anything
|
|
6
|
+
other than an array of numbers or strings easier.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
const { select, option, div, span, ul, li } = xinjs.elements
|
|
10
|
+
const { icons, makeSorter } = xinjsui
|
|
11
|
+
|
|
12
|
+
const people = [
|
|
13
|
+
{ first: 'Frasier', last: 'Crane', age: 38 },
|
|
14
|
+
{ first: 'Lilith', last: 'Crane', age: 37 },
|
|
15
|
+
{ first: 'Rebecca', last: 'Howe', age: 35 },
|
|
16
|
+
{ first: 'Woody', last: 'Boyd', age: 25 },
|
|
17
|
+
{ first: 'Sam', last: 'Malone', age: 40 },
|
|
18
|
+
{ first: 'Norm', last: 'Peterson', age: 38 },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const sorters = {
|
|
22
|
+
firstSort: makeSorter(person => [person.first]),
|
|
23
|
+
firstDescSort: makeSorter(person => [person.first], false),
|
|
24
|
+
nameSort: makeSorter(person => [person.last, person.first]),
|
|
25
|
+
ageFirst: makeSorter(person => [-person.age, person.last]),
|
|
26
|
+
ageLast: makeSorter(person => [person.age, person.first], [true, false]),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function person({first, last, age}) {
|
|
30
|
+
return li(`${first} ${last}, ${age}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const list = ul()
|
|
34
|
+
sortPicker = select(
|
|
35
|
+
option('Sort by first', {value: 'firstSort'}),
|
|
36
|
+
option('Sort by first (desc)', {value: 'firstDescSort'}),
|
|
37
|
+
option('Sort by last, first', {value: 'nameSort'}),
|
|
38
|
+
option('Sort by age (desc), first', {value: 'ageFirst'}),
|
|
39
|
+
option('Sort by age, last (desc)', {value: 'ageLast'}),
|
|
40
|
+
{
|
|
41
|
+
onChange: render,
|
|
42
|
+
value: 'nameSort'
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
function render () {
|
|
47
|
+
list.textContent = ''
|
|
48
|
+
list.append(...people.sort(sorters[sortPicker.value]).map(person))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
preview.append(
|
|
52
|
+
div(
|
|
53
|
+
sortPicker,
|
|
54
|
+
icons.chevronDown()
|
|
55
|
+
),
|
|
56
|
+
list
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
render()
|
|
60
|
+
```
|
|
61
|
+
```css
|
|
62
|
+
.preview {
|
|
63
|
+
padding: var(--spacing);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.preview div {
|
|
67
|
+
position: absolute;
|
|
68
|
+
top: var(--spacing);
|
|
69
|
+
right: var(--spacing);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Details
|
|
74
|
+
|
|
75
|
+
To create a sort callback that sorts by propA then propB (if propA is tied):
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
const sorter = makeSorter(
|
|
79
|
+
obj => [obj.propA, obj.propB]
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
As above, but sort descending:
|
|
84
|
+
```
|
|
85
|
+
const sorter = makeSorter(
|
|
86
|
+
obj => [obj.propA, obj.propB],
|
|
87
|
+
false
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
As above but propA is sorted ascending, propB descending
|
|
92
|
+
```
|
|
93
|
+
const sorter = makeSorter(
|
|
94
|
+
obj => [obj.propA, obj.propB],
|
|
95
|
+
[true, false]
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
*/
|
|
99
|
+
export function makeSorter(sortValuator, ascending = true) {
|
|
100
|
+
return (p, q) => {
|
|
101
|
+
const pSort = sortValuator(p);
|
|
102
|
+
const qSort = sortValuator(q);
|
|
103
|
+
for (const i in pSort) {
|
|
104
|
+
if (pSort[i] !== qSort[i]) {
|
|
105
|
+
const isAscending = Array.isArray(ascending)
|
|
106
|
+
? ascending[i] !== false
|
|
107
|
+
: ascending;
|
|
108
|
+
return isAscending
|
|
109
|
+
? pSort[i] > qSort[i]
|
|
110
|
+
? 1
|
|
111
|
+
: -1
|
|
112
|
+
: pSort[i] > qSort[i]
|
|
113
|
+
? -1
|
|
114
|
+
: 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return 0;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { test, expect } from 'bun:test';
|
|
3
|
+
import { makeSorter } from './make-sorter';
|
|
4
|
+
test('sorts simple values', () => {
|
|
5
|
+
const simpleSort = makeSorter((a) => [a]);
|
|
6
|
+
const array = [5, -1, 3];
|
|
7
|
+
expect([...array].sort(simpleSort)[0]).toBe(-1);
|
|
8
|
+
expect([...array].sort(simpleSort)[1]).toBe(3);
|
|
9
|
+
const strings = ['oh', 'be', 'a', 'fine'];
|
|
10
|
+
expect([...strings].sort(simpleSort)[0]).toBe('a');
|
|
11
|
+
expect([...strings].sort(simpleSort)[1]).toBe('be');
|
|
12
|
+
});
|
|
13
|
+
test('sorts simple values descending', () => {
|
|
14
|
+
const simpleSort = makeSorter((a) => [a], false);
|
|
15
|
+
const array = [5, -1, 3];
|
|
16
|
+
expect([...array].sort(simpleSort)[0]).toBe(5);
|
|
17
|
+
expect([...array].sort(simpleSort)[1]).toBe(3);
|
|
18
|
+
const strings = ['oh', 'be', 'a', 'fine'];
|
|
19
|
+
expect([...strings].sort(simpleSort)[0]).toBe('oh');
|
|
20
|
+
expect([...strings].sort(simpleSort)[1]).toBe('fine');
|
|
21
|
+
});
|
|
22
|
+
test('sorts compound objects', () => {
|
|
23
|
+
const sortNameThenId = makeSorter((a) => [
|
|
24
|
+
a.name,
|
|
25
|
+
a.id,
|
|
26
|
+
]);
|
|
27
|
+
const sortLowercaseNameThenId = makeSorter((a) => [a.name.toLocaleLowerCase(), a.id]);
|
|
28
|
+
const idThenName = makeSorter((a) => [
|
|
29
|
+
a.id,
|
|
30
|
+
a.name,
|
|
31
|
+
]);
|
|
32
|
+
const array = [
|
|
33
|
+
{ name: 'alice', id: 100 },
|
|
34
|
+
{ name: 'bob', id: 1 },
|
|
35
|
+
{ name: 'alice', id: 10 },
|
|
36
|
+
{ name: 'Zed', id: 5 },
|
|
37
|
+
{ name: 'smaug', id: -7 },
|
|
38
|
+
];
|
|
39
|
+
expect([...array].sort(sortNameThenId)[0].id).toBe(5);
|
|
40
|
+
expect([...array].sort(sortNameThenId)[2].id).toBe(100);
|
|
41
|
+
expect([...array].sort(sortNameThenId)[4].id).toBe(-7);
|
|
42
|
+
expect([...array].sort(sortLowercaseNameThenId)[0].id).toBe(10);
|
|
43
|
+
expect([...array].sort(sortLowercaseNameThenId)[2].id).toBe(1);
|
|
44
|
+
expect([...array].sort(sortLowercaseNameThenId)[4].id).toBe(5);
|
|
45
|
+
expect([...array].sort(idThenName)[0].id).toBe(-7);
|
|
46
|
+
expect([...array].sort(idThenName)[2].id).toBe(5);
|
|
47
|
+
expect([...array].sort(idThenName)[4].id).toBe(100);
|
|
48
|
+
});
|
package/dist/mapbox.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Component as WebComponent, ElementCreator } from 'tosijs';
|
|
2
|
+
export declare class MapBox extends WebComponent {
|
|
3
|
+
coords: string;
|
|
4
|
+
content: HTMLDivElement;
|
|
5
|
+
get map(): any;
|
|
6
|
+
mapStyle: string;
|
|
7
|
+
token: string;
|
|
8
|
+
static mapboxCSSAvailable: Promise<void>;
|
|
9
|
+
static mapboxAvailable?: Promise<any>;
|
|
10
|
+
private _map;
|
|
11
|
+
static styleSpec: {
|
|
12
|
+
':host': {
|
|
13
|
+
display: string;
|
|
14
|
+
position: string;
|
|
15
|
+
width: string;
|
|
16
|
+
height: string;
|
|
17
|
+
textAlign: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
constructor();
|
|
21
|
+
connectedCallback(): void;
|
|
22
|
+
render(): void;
|
|
23
|
+
}
|
|
24
|
+
export declare const mapBox: ElementCreator<MapBox>;
|