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.
Files changed (134) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/dist/ab-test.d.ts +14 -0
  4. package/dist/ab-test.js +116 -0
  5. package/dist/babylon-3d.d.ts +53 -0
  6. package/dist/babylon-3d.js +292 -0
  7. package/dist/bodymovin-player.d.ts +32 -0
  8. package/dist/bodymovin-player.js +172 -0
  9. package/dist/bp-loader.d.ts +1 -0
  10. package/dist/bp-loader.js +26 -0
  11. package/dist/carousel.d.ts +113 -0
  12. package/dist/carousel.js +308 -0
  13. package/dist/code-editor.d.ts +27 -0
  14. package/dist/code-editor.js +102 -0
  15. package/dist/color-input.d.ts +41 -0
  16. package/dist/color-input.js +112 -0
  17. package/dist/data-table.d.ts +79 -0
  18. package/dist/data-table.js +774 -0
  19. package/dist/drag-and-drop.d.ts +2 -0
  20. package/dist/drag-and-drop.js +386 -0
  21. package/dist/editable-rect.d.ts +97 -0
  22. package/dist/editable-rect.js +450 -0
  23. package/dist/filter-builder.d.ts +64 -0
  24. package/dist/filter-builder.js +468 -0
  25. package/dist/float.d.ts +18 -0
  26. package/dist/float.js +170 -0
  27. package/dist/form.d.ts +68 -0
  28. package/dist/form.js +466 -0
  29. package/dist/gamepad.d.ts +34 -0
  30. package/dist/gamepad.js +115 -0
  31. package/dist/icon-data.d.ts +312 -0
  32. package/dist/icon-data.js +308 -0
  33. package/dist/icon-types.d.ts +7 -0
  34. package/dist/icon-types.js +1 -0
  35. package/dist/icons.d.ts +17 -0
  36. package/dist/icons.js +374 -0
  37. package/dist/iife.js +69 -0
  38. package/dist/iife.js.map +49 -0
  39. package/dist/index-iife.d.ts +1 -0
  40. package/dist/index-iife.js +4 -0
  41. package/dist/index.d.ts +37 -0
  42. package/dist/index.js +37 -0
  43. package/dist/index.js.map +47 -0
  44. package/dist/live-example.d.ts +63 -0
  45. package/dist/live-example.js +611 -0
  46. package/dist/localize.d.ts +46 -0
  47. package/dist/localize.js +381 -0
  48. package/dist/make-sorter.d.ts +3 -0
  49. package/dist/make-sorter.js +119 -0
  50. package/dist/make-sorter.test.d.ts +1 -0
  51. package/dist/make-sorter.test.js +48 -0
  52. package/dist/mapbox.d.ts +24 -0
  53. package/dist/mapbox.js +161 -0
  54. package/dist/markdown-viewer.d.ts +17 -0
  55. package/dist/markdown-viewer.js +173 -0
  56. package/dist/match-shortcut.d.ts +9 -0
  57. package/dist/match-shortcut.js +13 -0
  58. package/dist/match-shortcut.test.d.ts +1 -0
  59. package/dist/match-shortcut.test.js +194 -0
  60. package/dist/menu.d.ts +60 -0
  61. package/dist/menu.js +614 -0
  62. package/dist/notifications.d.ts +106 -0
  63. package/dist/notifications.js +308 -0
  64. package/dist/password-strength.d.ts +35 -0
  65. package/dist/password-strength.js +302 -0
  66. package/dist/playwright.config.d.ts +9 -0
  67. package/dist/playwright.config.js +73 -0
  68. package/dist/pop-float.d.ts +10 -0
  69. package/dist/pop-float.js +231 -0
  70. package/dist/rating.d.ts +62 -0
  71. package/dist/rating.js +192 -0
  72. package/dist/rich-text.d.ts +35 -0
  73. package/dist/rich-text.js +296 -0
  74. package/dist/segmented.d.ts +80 -0
  75. package/dist/segmented.js +298 -0
  76. package/dist/select.d.ts +43 -0
  77. package/dist/select.js +427 -0
  78. package/dist/side-nav.d.ts +36 -0
  79. package/dist/side-nav.js +106 -0
  80. package/dist/size-break.d.ts +18 -0
  81. package/dist/size-break.js +118 -0
  82. package/dist/sizer.d.ts +34 -0
  83. package/dist/sizer.js +92 -0
  84. package/dist/src/ab-test.d.ts +14 -0
  85. package/dist/src/babylon-3d.d.ts +53 -0
  86. package/dist/src/bodymovin-player.d.ts +32 -0
  87. package/dist/src/bp-loader.d.ts +0 -0
  88. package/dist/src/carousel.d.ts +113 -0
  89. package/dist/src/code-editor.d.ts +27 -0
  90. package/dist/src/color-input.d.ts +41 -0
  91. package/dist/src/data-table.d.ts +79 -0
  92. package/dist/src/drag-and-drop.d.ts +2 -0
  93. package/dist/src/editable-rect.d.ts +97 -0
  94. package/dist/src/filter-builder.d.ts +64 -0
  95. package/dist/src/float.d.ts +18 -0
  96. package/dist/src/form.d.ts +68 -0
  97. package/dist/src/gamepad.d.ts +34 -0
  98. package/dist/src/icon-data.d.ts +309 -0
  99. package/dist/src/icon-types.d.ts +7 -0
  100. package/dist/src/icons.d.ts +17 -0
  101. package/dist/src/index.d.ts +37 -0
  102. package/dist/src/live-example.d.ts +51 -0
  103. package/dist/src/localize.d.ts +30 -0
  104. package/dist/src/make-sorter.d.ts +3 -0
  105. package/dist/src/mapbox.d.ts +24 -0
  106. package/dist/src/markdown-viewer.d.ts +15 -0
  107. package/dist/src/match-shortcut.d.ts +9 -0
  108. package/dist/src/menu.d.ts +60 -0
  109. package/dist/src/notifications.d.ts +106 -0
  110. package/dist/src/password-strength.d.ts +35 -0
  111. package/dist/src/pop-float.d.ts +10 -0
  112. package/dist/src/rating.d.ts +62 -0
  113. package/dist/src/rich-text.d.ts +28 -0
  114. package/dist/src/segmented.d.ts +80 -0
  115. package/dist/src/select.d.ts +43 -0
  116. package/dist/src/side-nav.d.ts +36 -0
  117. package/dist/src/size-break.d.ts +18 -0
  118. package/dist/src/sizer.d.ts +34 -0
  119. package/dist/src/tab-selector.d.ts +91 -0
  120. package/dist/src/tag-list.d.ts +37 -0
  121. package/dist/src/track-drag.d.ts +5 -0
  122. package/dist/src/version.d.ts +1 -0
  123. package/dist/src/via-tag.d.ts +2 -0
  124. package/dist/tab-selector.d.ts +91 -0
  125. package/dist/tab-selector.js +326 -0
  126. package/dist/tag-list.d.ts +37 -0
  127. package/dist/tag-list.js +375 -0
  128. package/dist/track-drag.d.ts +5 -0
  129. package/dist/track-drag.js +143 -0
  130. package/dist/version.d.ts +1 -0
  131. package/dist/version.js +1 -0
  132. package/dist/via-tag.d.ts +2 -0
  133. package/dist/via-tag.js +102 -0
  134. package/package.json +58 -0
@@ -0,0 +1,43 @@
1
+ import { Component as WebComponent, ElementCreator } from 'tosijs';
2
+ import { MenuItem } from './menu';
3
+ type OptionRequest = () => Promise<string | undefined>;
4
+ export interface SelectOption {
5
+ icon?: string | HTMLElement;
6
+ caption: string;
7
+ value: string | OptionRequest;
8
+ }
9
+ export interface SelectOptionSubmenu {
10
+ icon?: string | HTMLElement;
11
+ caption: string;
12
+ options: SelectOptions;
13
+ }
14
+ export type SelectOptions = Array<string | null | SelectOption | SelectOptionSubmenu>;
15
+ export declare class XinSelect extends WebComponent {
16
+ editable: boolean;
17
+ showIcon: boolean;
18
+ hideCaption: boolean;
19
+ options: string | SelectOptions;
20
+ value: string;
21
+ placeholder: string;
22
+ filter: string;
23
+ localized: boolean;
24
+ private setValue;
25
+ private getValue;
26
+ get selectOptions(): SelectOptions;
27
+ private buildOptionMenuItem;
28
+ get optionsMenu(): MenuItem[];
29
+ handleChange: (event: Event) => void;
30
+ handleKey: (event: KeyboardEvent) => void;
31
+ filterMenu: (...args: any[]) => void;
32
+ popOptions: (event?: Event) => void;
33
+ content: () => HTMLButtonElement[];
34
+ constructor();
35
+ get allOptions(): SelectOption[];
36
+ findOption(): SelectOption;
37
+ localeChanged: () => void;
38
+ connectedCallback(): void;
39
+ disconnectedCallback(): void;
40
+ render(): void;
41
+ }
42
+ export declare const xinSelect: ElementCreator<XinSelect>;
43
+ export {};
package/dist/select.js ADDED
@@ -0,0 +1,427 @@
1
+ /*#
2
+ # select
3
+
4
+ `<xin-select>` (`xinSelect` is the `ElementCreator`) is a replacement for the lamentable
5
+ built in `<select>` element that addresses its various shortcomings.
6
+
7
+ - since `<xin-select>` is powered by `popMenu`, and supports separators and submenus.
8
+ - options can have icons.
9
+ - `<xin-select>` will retain and display a value even if the matching option is missing.
10
+ - its displayed value can be made `editable`, allowing use as a "combo box".
11
+ - options can have `async` callbacks that return a value.
12
+ - picking an item triggers an `action` event even if the value hasn't changed.
13
+ - available options are set via the `options` attribute or the element's `options` property (not `<option>` elements)
14
+
15
+ ```html
16
+ <xin-select
17
+ title="simple select"
18
+ options="this,that,,the other"
19
+ value="not an option!"
20
+ ></xin-select><br>
21
+ <xin-select
22
+ show-icon
23
+ title="has captions"
24
+ class="captions"
25
+ value="image"
26
+ ></xin-select><br>
27
+ <xin-select
28
+ show-icon
29
+ title="combo select with icons"
30
+ class="icons"
31
+ editable
32
+ placeholder="pick an icon"
33
+ ></xin-select><br>
34
+ <xin-select
35
+ show-icon
36
+ hide-caption
37
+ title="icons only"
38
+ class="icons-only"
39
+ placeholder="pick an icon"
40
+ ></xin-select>
41
+ <pre contenteditable>Select some text in here…
42
+ …to check for focus stealing</pre>
43
+ ```
44
+ ```js
45
+ const { icons } = xinjsui
46
+
47
+ const captions = preview.querySelector('.captions')
48
+
49
+ captions.options = [
50
+ {
51
+ caption: 'a heading',
52
+ value: 'heading'
53
+ },
54
+ {
55
+ caption: 'a paragraph',
56
+ value: 'paragraph'
57
+ },
58
+ null,
59
+ {
60
+ caption: 'choose some other',
61
+ options: [
62
+ {
63
+ icon: 'image',
64
+ caption: 'an image',
65
+ value: 'image'
66
+ },
67
+ {
68
+ icon: 'fileText',
69
+ caption: 'a text file',
70
+ value: 'text',
71
+ },
72
+ {
73
+ icon: 'video',
74
+ caption: 'a video',
75
+ value: 'video'
76
+ },
77
+ null,
78
+ {
79
+ caption: 'anything goes…',
80
+ value: () => prompt('Enter your other', 'other') || undefined
81
+ },
82
+ {
83
+ caption: 'brother… (after 1s delay)',
84
+ value: async () => new Promise(resolve => {
85
+ setTimeout(() => resolve('brother'), 1000)
86
+ })
87
+ }
88
+ ]
89
+ }
90
+ ]
91
+
92
+ const iconsSelect = preview.querySelector('.icons')
93
+ const iconsOnly = preview.querySelector('.icons-only')
94
+
95
+ iconsSelect.options = iconsOnly.options = Object.keys(icons).sort().map(icon =>({
96
+ icon,
97
+ caption: icon,
98
+ value: icon
99
+ }))
100
+
101
+ preview.addEventListener('action', (event) => {
102
+ console.log(event.target.title, 'user picked', event.target.value)
103
+ }, true)
104
+
105
+ preview.addEventListener('change', (event) => {
106
+ console.log(event.target.title, 'changed to', event.target.value)
107
+ }, true)
108
+ ```
109
+ <xin-css-var-editor element-selector="xin-select"></xin-css-var-editor>
110
+
111
+ ## `options`
112
+
113
+ type OptionRequest = () => Promise<string | undefined>
114
+
115
+ export interface SelectOption {
116
+ icon?: string | HTMLElement
117
+ caption: string
118
+ value: string | OptionRequest
119
+ }
120
+
121
+ export interface SelectOptionSubmenu {
122
+ icon?: string | HTMLElement
123
+ caption: string
124
+ options: SelectOptions
125
+ }
126
+
127
+ export type SelectOptions = Array<string | null | SelectOption | SelectOptionSubmenu>
128
+
129
+ A `<xin-select>` can be assigned `options` as a string of comma-delimited choices,
130
+ or be provided a `SelectOptions` array (which allows for submenus, separators, etc.).
131
+
132
+ ## Attributes
133
+
134
+ `<xin-select>` supports several attributes:
135
+
136
+ - `editable` lets the user directly edit the value (like a "combo box").
137
+ - `show-icon` displays the icon corresponding to the currently selected value.
138
+ - `hide-caption` hides the caption.
139
+ - `placeholder` allows you to set a placeholder.
140
+ - `options` allows you to assign options as a comma-delimited string attribute.
141
+
142
+ ## Events
143
+
144
+ Picking an option triggers an `action` event (whether or not this changes the value).
145
+
146
+ Changing the value, either by typing in an editable `<xin-select>` or picking a new
147
+ value triggers a `change` event.
148
+
149
+ You can look at the console to see the events triggered by the second example.
150
+
151
+ ## Localization
152
+
153
+ `<xin-select>` supports the `localized` attribute which automatically localizes
154
+ options.
155
+
156
+ ```html
157
+ <xin-select
158
+ localized
159
+ placeholder="localized placeholder"
160
+ options="yes,no,,moderate"
161
+ ></xin-select>
162
+ ```
163
+ */
164
+ import { Component as WebComponent, elements, vars, throttle, } from 'xinjs';
165
+ import { icons } from './icons';
166
+ import { popMenu, removeLastMenu } from './menu';
167
+ import { localize, XinLocalized } from './localize';
168
+ const { button, span, input } = elements;
169
+ const hasValue = (options, value) => {
170
+ return !!options.find((option) => {
171
+ if (option === null || value == null) {
172
+ return false;
173
+ }
174
+ else if (Array.isArray(option)) {
175
+ return hasValue(option, value);
176
+ }
177
+ else if (option.value === value || option === value) {
178
+ return true;
179
+ }
180
+ });
181
+ };
182
+ export class XinSelect extends WebComponent {
183
+ editable = false;
184
+ showIcon = false;
185
+ hideCaption = false;
186
+ options = '';
187
+ value = '';
188
+ placeholder = '';
189
+ filter = '';
190
+ localized = false;
191
+ setValue = (value, triggerAction = false) => {
192
+ if (this.value !== value) {
193
+ this.value = value;
194
+ this.queueRender(true);
195
+ }
196
+ if (triggerAction) {
197
+ this.dispatchEvent(new Event('action'));
198
+ }
199
+ };
200
+ getValue = () => this.value;
201
+ get selectOptions() {
202
+ return typeof this.options === 'string'
203
+ ? this.options.split(',').map((option) => option.trim() || null)
204
+ : this.options;
205
+ }
206
+ buildOptionMenuItem = (option) => {
207
+ if (option === null) {
208
+ return null;
209
+ }
210
+ const { setValue, getValue } = this;
211
+ let icon;
212
+ let caption;
213
+ let value;
214
+ if (typeof option === 'string') {
215
+ caption = value = option;
216
+ }
217
+ else {
218
+ ;
219
+ ({ icon, caption, value } = option);
220
+ }
221
+ if (this.localized) {
222
+ caption = localize(caption);
223
+ }
224
+ const { options } = option;
225
+ if (options) {
226
+ return {
227
+ icon,
228
+ caption,
229
+ checked: () => hasValue(options, getValue()),
230
+ menuItems: options.map(this.buildOptionMenuItem),
231
+ };
232
+ }
233
+ return {
234
+ icon,
235
+ caption,
236
+ checked: () => getValue() === value,
237
+ action: typeof value === 'function'
238
+ ? async () => {
239
+ const newValue = await value();
240
+ if (newValue !== undefined) {
241
+ setValue(newValue, true);
242
+ }
243
+ }
244
+ : () => {
245
+ if (typeof value === 'string') {
246
+ setValue(value, true);
247
+ }
248
+ },
249
+ };
250
+ };
251
+ get optionsMenu() {
252
+ const options = this.selectOptions.map(this.buildOptionMenuItem);
253
+ if (this.filter === '') {
254
+ return options;
255
+ }
256
+ const showOption = (option) => {
257
+ if (option === null) {
258
+ return true;
259
+ }
260
+ else if (option.menuItems) {
261
+ ;
262
+ option.menuItems = option.menuItems.filter(showOption);
263
+ return option.menuItems.length > 0;
264
+ }
265
+ else {
266
+ return option.caption.toLocaleLowerCase().includes(this.filter);
267
+ }
268
+ };
269
+ return options.filter(showOption);
270
+ }
271
+ handleChange = (event) => {
272
+ const { value } = this.parts;
273
+ const newValue = value.value || '';
274
+ if (this.value !== String(newValue)) {
275
+ this.value = newValue;
276
+ this.dispatchEvent(new Event('change'));
277
+ }
278
+ this.filter = '';
279
+ event.stopPropagation();
280
+ event.preventDefault();
281
+ };
282
+ handleKey = (event) => {
283
+ if (event.key === 'Enter') {
284
+ event.preventDefault();
285
+ }
286
+ };
287
+ filterMenu = throttle(() => {
288
+ this.filter = this.parts.value.value.toLocaleLowerCase();
289
+ removeLastMenu(0);
290
+ this.popOptions();
291
+ });
292
+ popOptions = (event) => {
293
+ if (event && event.type === 'click') {
294
+ this.filter = '';
295
+ }
296
+ this.poppedOptions = this.optionsMenu;
297
+ popMenu({
298
+ target: this,
299
+ menuItems: this.poppedOptions,
300
+ });
301
+ };
302
+ content = () => [
303
+ button({
304
+ onClick: this.popOptions,
305
+ }, span(), input({
306
+ part: 'value',
307
+ value: this.value,
308
+ tabindex: 0,
309
+ onKeydown: this.handleKey,
310
+ onInput: this.filterMenu,
311
+ onChange: this.handleChange,
312
+ }), icons.chevronDown()),
313
+ ];
314
+ constructor() {
315
+ super();
316
+ this.initAttributes('options', 'editable', 'placeholder', 'showIcon', 'hideCaption', 'localized');
317
+ }
318
+ get allOptions() {
319
+ const all = [];
320
+ function flatten(some) {
321
+ for (const option of some) {
322
+ if (typeof option === 'string') {
323
+ all.push({ caption: option, value: option });
324
+ }
325
+ else if (option?.value) {
326
+ all.push(option);
327
+ }
328
+ else if (option?.options) {
329
+ flatten(option.options);
330
+ }
331
+ }
332
+ }
333
+ flatten(this.selectOptions);
334
+ return all;
335
+ }
336
+ findOption() {
337
+ const found = this.allOptions.find((option) => option.value === this.value);
338
+ return found || { caption: this.value, value: this.value };
339
+ }
340
+ localeChanged = () => {
341
+ this.queueRender();
342
+ };
343
+ connectedCallback() {
344
+ super.connectedCallback();
345
+ if (this.localized) {
346
+ XinLocalized.allInstances.add(this);
347
+ }
348
+ }
349
+ disconnectedCallback() {
350
+ super.disconnectedCallback();
351
+ if (this.localized) {
352
+ XinLocalized.allInstances.delete(this);
353
+ }
354
+ }
355
+ render() {
356
+ super.render();
357
+ const { value } = this.parts;
358
+ const icon = value.previousElementSibling;
359
+ const option = this.findOption();
360
+ let newIcon = span();
361
+ value.value = this.localized ? localize(option.caption) : option.caption;
362
+ if (option.icon) {
363
+ if (option.icon instanceof HTMLElement) {
364
+ newIcon = option.icon.cloneNode(true);
365
+ }
366
+ else {
367
+ newIcon = icons[option.icon]();
368
+ }
369
+ }
370
+ icon.replaceWith(newIcon);
371
+ value.setAttribute('placeholder', this.localized ? localize(this.placeholder) : this.placeholder);
372
+ value.style.pointerEvents = this.editable ? '' : 'none';
373
+ value.readOnly = !this.editable;
374
+ }
375
+ }
376
+ export const xinSelect = XinSelect.elementCreator({
377
+ tag: 'xin-select',
378
+ styleSpec: {
379
+ ':host': {
380
+ '--gap': '8px',
381
+ '--touch-size': '44px',
382
+ '--padding': '0 8px',
383
+ '--value-padding': '0 8px',
384
+ '--icon-width': '24px',
385
+ '--fieldWidth': '140px',
386
+ display: 'inline-block',
387
+ position: 'relative',
388
+ },
389
+ ':host button': {
390
+ display: 'grid',
391
+ alignItems: 'center',
392
+ gap: vars.gap,
393
+ textAlign: 'left',
394
+ height: vars.touchSize,
395
+ padding: vars.padding,
396
+ gridTemplateColumns: `auto ${vars.iconWidth}`,
397
+ position: 'relative',
398
+ },
399
+ ':host[show-icon] button': {
400
+ gridTemplateColumns: `${vars.iconWidth} auto ${vars.iconWidth}`,
401
+ },
402
+ ':host[hide-caption] button': {
403
+ gridTemplateColumns: `${vars.iconWidth} ${vars.iconWidth}`,
404
+ },
405
+ ':host:not([show-icon]) button > :first-child': {
406
+ display: 'none',
407
+ },
408
+ ':host[hide-caption] button > :nth-child(2)': {
409
+ display: 'none',
410
+ },
411
+ ':host [part="value"]': {
412
+ width: vars.fieldWidth,
413
+ padding: vars.valuePadding,
414
+ height: vars.touchSize,
415
+ lineHeight: vars.touchSize,
416
+ boxShadow: 'none',
417
+ whiteSpace: 'nowrap',
418
+ outline: 'none',
419
+ background: 'transparent',
420
+ },
421
+ ':host [part="value"]:not(:focus)': {
422
+ overflow: 'hidden',
423
+ textOverflow: 'ellipsis',
424
+ background: 'transparent',
425
+ },
426
+ },
427
+ });
@@ -0,0 +1,36 @@
1
+ import { Component, ElementCreator } from 'tosijs';
2
+ export declare class SideNav extends Component {
3
+ minSize: number;
4
+ navSize: number;
5
+ compact: boolean;
6
+ content: HTMLSlotElement[];
7
+ private _contentVisible;
8
+ get contentVisible(): boolean;
9
+ set contentVisible(visible: boolean);
10
+ static styleSpec: {
11
+ ':host': {
12
+ display: string;
13
+ gridTemplateColumns: string;
14
+ gridTemplateRows: string;
15
+ position: string;
16
+ margin: string;
17
+ transition: string;
18
+ };
19
+ ':host slot': {
20
+ position: string;
21
+ };
22
+ ':host slot:not([name])': {
23
+ display: string;
24
+ };
25
+ ':host slot[name="nav"]': {
26
+ display: string;
27
+ };
28
+ };
29
+ onResize: () => void;
30
+ private observer;
31
+ connectedCallback(): void;
32
+ disconnectedCallback(): void;
33
+ constructor();
34
+ render(): void;
35
+ }
36
+ export declare const sideNav: ElementCreator<SideNav>;
@@ -0,0 +1,106 @@
1
+ /*#
2
+ # sidebar
3
+
4
+ The default layout for iOS / iPadOS apps is to hide the sidebar when displaying content on small
5
+ screens, and display the sidebar when space is available (with the user able to explicitly hide
6
+ the sidebar if so desired). `<xin-sidenav>` provides this functionality.
7
+
8
+ `<xin-sidenav>` is used to handle the layout of the documentation tab panel.
9
+
10
+ `<xin-sidenav>`'s behavior is controlled by two attributes, `minSize` is the point at which it will toggle between showing the navigation
11
+ sidebar and content, while `navSize` is the width of the sidebar. You can interrogate its `compact` property to find out if it's
12
+ currently in `compact` form.
13
+ */
14
+ import { Component, elements, varDefault } from 'xinjs';
15
+ const { slot } = elements;
16
+ export class SideNav extends Component {
17
+ minSize = 800;
18
+ navSize = 200;
19
+ compact = false;
20
+ content = [slot({ name: 'nav', part: 'nav' }), slot({ part: 'content' })];
21
+ _contentVisible = false;
22
+ get contentVisible() {
23
+ return this._contentVisible;
24
+ }
25
+ set contentVisible(visible) {
26
+ this._contentVisible = visible;
27
+ this.queueRender();
28
+ }
29
+ static styleSpec = {
30
+ ':host': {
31
+ display: 'grid',
32
+ gridTemplateColumns: `${varDefault.navWidth('50%')} ${varDefault.contentWidth('50%')}`,
33
+ gridTemplateRows: '100%',
34
+ position: 'relative',
35
+ margin: varDefault.margin('0 0 0 -100%'),
36
+ transition: varDefault.sideNavTransition('0.25s ease-out'),
37
+ },
38
+ ':host slot': {
39
+ position: 'relative',
40
+ },
41
+ ':host slot:not([name])': {
42
+ display: 'block',
43
+ },
44
+ ':host slot[name="nav"]': {
45
+ display: 'block',
46
+ },
47
+ };
48
+ onResize = () => {
49
+ const { content } = this.parts;
50
+ const parent = this.offsetParent;
51
+ if (parent === null) {
52
+ return;
53
+ }
54
+ this.compact = parent.offsetWidth < this.minSize;
55
+ const empty = [...this.childNodes].find((node) => node instanceof Element ? node.getAttribute('slot') !== 'nav' : true) === undefined;
56
+ if (empty) {
57
+ this.style.setProperty('--nav-width', '100%');
58
+ this.style.setProperty('--content-width', '0%');
59
+ return;
60
+ }
61
+ if (!this.compact) {
62
+ content.classList.add('-xin-sidenav-visible');
63
+ this.style.setProperty('--nav-width', `${this.navSize}px`);
64
+ this.style.setProperty('--content-width', `calc(100% - ${this.navSize}px)`);
65
+ this.style.setProperty('--margin', '0');
66
+ }
67
+ else {
68
+ content.classList.remove('-xin-sidenav-visible');
69
+ this.style.setProperty('--nav-width', '50%');
70
+ this.style.setProperty('--content-width', '50%');
71
+ if (this.contentVisible) {
72
+ this.style.setProperty('--margin', '0 0 0 -100%');
73
+ }
74
+ else {
75
+ this.style.setProperty('--margin', '0 -100% 0 0');
76
+ }
77
+ }
78
+ };
79
+ observer;
80
+ connectedCallback() {
81
+ super.connectedCallback();
82
+ this.contentVisible = this.parts.content.childNodes.length === 0;
83
+ globalThis.addEventListener('resize', this.onResize);
84
+ this.observer = new MutationObserver(this.onResize);
85
+ this.observer.observe(this, { childList: true });
86
+ this.style.setProperty('--side-nav-transition', '0s');
87
+ setTimeout(() => {
88
+ this.style.removeProperty('--side-nav-transition');
89
+ }, 250);
90
+ }
91
+ disconnectedCallback() {
92
+ super.disconnectedCallback();
93
+ this.observer.disconnect();
94
+ }
95
+ constructor() {
96
+ super();
97
+ this.initAttributes('minSize', 'navSize', 'compact');
98
+ }
99
+ render() {
100
+ super.render();
101
+ this.onResize();
102
+ }
103
+ }
104
+ export const sideNav = SideNav.elementCreator({
105
+ tag: 'xin-sidenav',
106
+ });
@@ -0,0 +1,18 @@
1
+ import { Component as WebComponent, ElementCreator } from 'tosijs';
2
+ export declare class SizeBreak extends WebComponent {
3
+ minWidth: number;
4
+ minHeight: number;
5
+ value: 'normal' | 'small';
6
+ content: HTMLSlotElement[];
7
+ static styleSpec: {
8
+ ':host': {
9
+ display: string;
10
+ position: string;
11
+ };
12
+ };
13
+ constructor();
14
+ onResize: () => void;
15
+ connectedCallback(): void;
16
+ disconnectedCallback(): void;
17
+ }
18
+ export declare const sizeBreak: ElementCreator<SizeBreak>;