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
package/dist/menu.js ADDED
@@ -0,0 +1,614 @@
1
+ /*#
2
+ # menu
3
+
4
+ Being able to pop a menu up anywhere is just so nice, and `xinjs-ui` allows menus
5
+ to be generated on-the-fly, and even supports hierarchical menus.
6
+
7
+ ## popMenu and `<xin-menu>`
8
+
9
+ `popMenu({target, menuItems, …})` will spawn a menu from a target.
10
+
11
+ The `<xin-menu>` component places creates a trigger button, hosts
12
+ menuItems, and (because it persists in the DOM) supports keyboard
13
+ shortcuts.
14
+
15
+ ```js
16
+ const { popMenu, localize, xinMenu, postNotification, xinLocalized, icons } = xinjsui
17
+ const { elements } = xinjs
18
+
19
+ let picked = ''
20
+ let testingEnabled = false
21
+
22
+ const menuItems = [
23
+ {
24
+ icon: 'thumbsUp',
25
+ caption: 'Like',
26
+ shortcut: '^L',
27
+ action() {
28
+ postNotification({
29
+ message: 'I like it!',
30
+ icon: 'thumbsUp',
31
+ duration: 1
32
+ })
33
+ }
34
+ },
35
+ {
36
+ icon: 'heart',
37
+ caption: 'Love',
38
+ shortcut: '⌘⇧L',
39
+ action() {
40
+ postNotification({
41
+ type: 'success',
42
+ message: 'I LOVE it!',
43
+ icon: 'heart',
44
+ duration: 1
45
+ })
46
+ }
47
+ },
48
+ {
49
+ icon: 'thumbsDown',
50
+ caption: 'dislike',
51
+ shortcut: '⌘D',
52
+ action() {
53
+ postNotification({
54
+ type: 'error',
55
+ message: 'Awwwwwww…',
56
+ icon: 'thumbsDown',
57
+ duration: 1
58
+ })
59
+ }
60
+ },
61
+ null, // separator
62
+ {
63
+ caption: localize('Localized placeholder'),
64
+ action() {
65
+ alert(localize('Localized placeholder'))
66
+ }
67
+ },
68
+ {
69
+ icon: elements.span('🥹'),
70
+ caption: 'Also see…',
71
+ menuItems: [
72
+ {
73
+ icon: elements.span('😳'),
74
+ caption: 'And that’s not all…',
75
+ menuItems: [
76
+ {
77
+ icon: 'externalLink',
78
+ caption: 'timezones',
79
+ action: 'https://timezones.xinjs.net/'
80
+ },
81
+ {
82
+ icon: 'externalLink',
83
+ caption: 'b8rjs',
84
+ action: 'https://b8rjs.com'
85
+ },
86
+ ]
87
+ },
88
+ {
89
+ icon: 'xinjs',
90
+ caption: 'xinjs',
91
+ action: 'https://xinjs.net'
92
+ },
93
+ {
94
+ icon: 'xinie',
95
+ caption: 'xinie',
96
+ action: 'https://xinie.net'
97
+ },
98
+ ]
99
+ },
100
+ {
101
+ icon: testingEnabled ? 'check' : '',
102
+ caption: 'Testing Enabled',
103
+ action() {
104
+ testingEnabled = !testingEnabled
105
+ }
106
+ },
107
+ {
108
+ caption: 'Testing…',
109
+ enabled() {
110
+ return testingEnabled
111
+ },
112
+ menuItems: [
113
+ {
114
+ caption: 'one',
115
+ checked: () => picked === 'one',
116
+ action () {
117
+ picked = 'one'
118
+ }
119
+ },
120
+ {
121
+ caption: 'two',
122
+ checked: () => picked === 'two',
123
+ action () {
124
+ picked = 'two'
125
+ }
126
+ },
127
+ {
128
+ caption: 'three',
129
+ checked: () => picked === 'three',
130
+ action () {
131
+ picked = 'three'
132
+ }
133
+ }
134
+ ]
135
+ }
136
+ ]
137
+
138
+ preview.addEventListener('click', (event) => {
139
+ if (!event.target.closest('button')) {
140
+ return
141
+ }
142
+ popMenu({
143
+ target: event.target,
144
+ menuItems
145
+ })
146
+ })
147
+
148
+ preview.append(
149
+ xinMenu(
150
+ {
151
+ menuItems,
152
+ localized: true,
153
+ },
154
+ xinLocalized('Menu'),
155
+ icons.chevronDown()
156
+ )
157
+ )
158
+ ```
159
+ ```html
160
+ <button title="menu test">
161
+ <xin-icon icon="moreVertical"></xin-icon>
162
+ </button>
163
+ <button title="menu test from bottom-right" style="position: absolute; bottom: 0; right: 0">
164
+ <xin-icon icon="moreVertical"></xin-icon>
165
+ </button>
166
+ ```
167
+ ```css
168
+ .preview button {
169
+ min-width: 44px;
170
+ text-align: center;
171
+ height: 44px;
172
+ margin: 5px;
173
+ }
174
+ ```
175
+
176
+ ## Overflow test
177
+
178
+ ```js
179
+ const { popMenu, icons, postNotification } = xinjsui
180
+ const { elements } = xinjs
181
+
182
+ preview.querySelector('button').addEventListener('click', (event) => {
183
+ popMenu({
184
+ target: event.target,
185
+ menuItems: Object.keys(icons).map(icon => ({
186
+ icon,
187
+ caption: icon,
188
+ action() {
189
+ postNotification({
190
+ icon: icon,
191
+ message: icon,
192
+ duration: 1
193
+ })
194
+ }
195
+ }))
196
+ })
197
+ })
198
+ ```
199
+ ```html
200
+ <button title="big menu test" style="position: absolute; top: 0; left: 0">
201
+ Big Menu Test
202
+ </button>
203
+ ```
204
+
205
+ ## popMenu({target, width, menuItems…})
206
+
207
+ ```
208
+ export interface PopMenuOptions {
209
+ target: HTMLElement
210
+ menuItems: MenuItem[]
211
+ width?: string | number
212
+ position?: FloatPosition
213
+ submenuDepth?: number // don't set this, it's set internally by popMenu
214
+ submenuOffset?: { x: number; y: number }
215
+ localized?: boolean
216
+ }
217
+ ```
218
+
219
+ `popMenu` will spawn a menu on a target element. A menu is just a `MenuItem[]`.
220
+
221
+ ## MenuItem
222
+
223
+ A `MenuItem` can be one of three things:
224
+
225
+ - `null` denotes a separator
226
+ - `MenuAction` denotes a labeled button or `<a>` tag based on whether the `action` provided
227
+ is a url (string) or an event handler (function).
228
+ - `SubMenu` is a submenu.
229
+
230
+ ### MenuAction
231
+
232
+ Note that popMenu does not implement shortcuts for you (yet!).
233
+
234
+ ```
235
+ interface MenuAction {
236
+ caption: string
237
+ shortcut?: string
238
+ checked?: () => boolean
239
+ enabled?: () => boolean
240
+ action: ActionCallback | string
241
+ icon?: string | Element
242
+ }
243
+ ```
244
+
245
+ ### SubMenu
246
+
247
+ ```
248
+ interface SubMenu {
249
+ caption: string
250
+ enabled?: () => boolean
251
+ menuItems: MenuItem[]
252
+ icon?: string | Element
253
+ }
254
+ ```
255
+
256
+ ### Keyboard Shortcuts
257
+
258
+ If a menu is embodied in a `<xin-menu>` it is supported by keyboard
259
+ shortcuts. Both text and symbolic shortcut descriptions are supported,
260
+ e.g.
261
+
262
+ - `⌘C` or `meta-C`
263
+ - `⇧P` for `shift-P`
264
+ - `^F` or `ctrl-f`
265
+ - `⌥x`, `⎇x`, `alt-x` or `option-x`
266
+
267
+ ## Localization
268
+
269
+ If you set `localized: true` in `PopMenuOptions` then menu captions will be be
270
+ passed through `localize`. You'll need to provide the appropriate localized strings,
271
+ of course.
272
+
273
+ > `<xin-menu>` supports the `localized` attribute but it doesn't localize
274
+ > its trigger button.
275
+
276
+ To see this in action, see the example below, or look at the
277
+ [table example](?data-table.ts). It uses a `localized` menu
278
+ to render column names when you show hidden columns.
279
+
280
+ ```js
281
+ const { elements } = xinjs
282
+ const { xinLocalized, localize, icons, popMenu, postNotification } = xinjsui
283
+ const { button } = elements
284
+ const makeItem = s => ({
285
+ caption: s,
286
+ action() {
287
+ postNotification({
288
+ message: localize(s),
289
+ duration: 1
290
+ })
291
+ }
292
+ })
293
+ const target = button(
294
+ {
295
+ onClick(event) {
296
+ popMenu({
297
+ target: event.target.closest('button'),
298
+ localized: true,
299
+ menuItems: [
300
+ makeItem('New'),
301
+ makeItem('Open...'),
302
+ makeItem('Save'),
303
+ makeItem('Close'),
304
+ ]
305
+ })
306
+ }
307
+ },
308
+ xinLocalized(
309
+ { style: { marginRight: '5px' }},
310
+ 'menu'
311
+ ),
312
+ icons.chevronDown()
313
+ )
314
+ preview.append(target)
315
+ ```
316
+
317
+ ## Why another menu library?!
318
+
319
+ Support for menus is sadly lacking in HTML, and unfortunately there's a huge conceptual problem
320
+ with menus implemented the way React and React-influenced libraries work, i.e. you need
321
+ to have an instance of a menu "wrapped around" the DOM element that triggers it, whereas
322
+ a better approach (and one dating back at least as far as the original Mac UI) is to treat
323
+ a menu as a separate resource that can be instantiated on demand.
324
+
325
+ A simple example where this becomes really obvious is if you want to associate a "more options"
326
+ menu with every row of a large table. Either you end up having an enormous DOM (virtual or otherwise)
327
+ or you have to painfully swap out components on-the-fly.
328
+
329
+ And, finally, submenus are darn useful for any serious app.
330
+
331
+ For this reason, `xinjs-ui` has its own menu implementation.
332
+ */
333
+ import { elements, varDefault, vars, StyleSheet, Component, } from 'xinjs';
334
+ import { popFloat } from './pop-float';
335
+ import { icons } from './icons';
336
+ import { localize } from './localize';
337
+ import { matchShortcut } from './match-shortcut';
338
+ const { div, button, span, a, xinSlot } = elements;
339
+ StyleSheet('xin-menu-helper', {
340
+ '.xin-menu': {
341
+ overflow: 'hidden auto',
342
+ maxHeight: `calc(${vars.maxHeight} - ${varDefault.menuInset('8px')})`,
343
+ borderRadius: vars.spacing50,
344
+ background: varDefault.menuBg('#fafafa'),
345
+ boxShadow: varDefault.menuShadow(`${vars.spacing13} ${vars.spacing50} ${vars.spacing} #0004`),
346
+ },
347
+ '.xin-menu > div': {
348
+ width: varDefault.menuWidth('auto'),
349
+ },
350
+ '.xin-menu-trigger': {
351
+ paddingLeft: 0,
352
+ paddingRight: 0,
353
+ minWidth: varDefault.touchSize('48px'),
354
+ },
355
+ '.xin-menu-separator': {
356
+ display: 'inline-block',
357
+ content: ' ',
358
+ height: '1px',
359
+ width: '100%',
360
+ background: varDefault.menuSeparatorColor('#2224'),
361
+ margin: varDefault.menuSeparatorMargin('8px 0'),
362
+ },
363
+ '.xin-menu-item': {
364
+ boxShadow: 'none',
365
+ border: 'none !important',
366
+ display: 'grid',
367
+ alignItems: 'center',
368
+ justifyContent: 'flex-start',
369
+ textDecoration: 'none',
370
+ gridTemplateColumns: '0px 1fr 30px',
371
+ width: '100%',
372
+ gap: 0,
373
+ background: 'transparent',
374
+ padding: varDefault.menuItemPadding('0 16px'),
375
+ height: varDefault.menuItemHeight('48px'),
376
+ lineHeight: varDefault.menuItemHeight('48px'),
377
+ textAlign: 'left',
378
+ },
379
+ '.xin-menu-item, .xin-menu-item > span': {
380
+ color: varDefault.menuItemColor('#222'),
381
+ },
382
+ '.xin-menu-with-icons .xin-menu-item': {
383
+ gridTemplateColumns: '30px 1fr 30px',
384
+ },
385
+ '.xin-menu-item svg': {
386
+ stroke: varDefault.menuItemIconColor('#222'),
387
+ },
388
+ '.xin-menu-item.xin-menu-item-checked': {
389
+ background: varDefault.menuItemHoverBg('#eee'),
390
+ },
391
+ '.xin-menu-item > span:nth-child(2)': {
392
+ whiteSpace: 'nowrap',
393
+ overflow: 'hidden',
394
+ textOverflow: 'ellipsis',
395
+ textAlign: 'left',
396
+ },
397
+ '.xin-menu-item:hover': {
398
+ // chrome rendering bug
399
+ boxShadow: 'none !important',
400
+ background: varDefault.menuItemHoverBg('#eee'),
401
+ },
402
+ '.xin-menu-item:active': {
403
+ // chrome rendering bug
404
+ boxShadow: 'none !important',
405
+ background: varDefault.menuItemActiveBg('#aaa'),
406
+ color: varDefault.menuItemActiveColor('#000'),
407
+ },
408
+ '.xin-menu-item:active svg': {
409
+ stroke: varDefault.menuItemIconActiveColor('#000'),
410
+ },
411
+ });
412
+ export const createMenuAction = (item, options) => {
413
+ const checked = (item.checked && item.checked() && 'check') || false;
414
+ let icon = item?.icon || checked || span(' ');
415
+ if (typeof icon === 'string') {
416
+ icon = icons[icon]();
417
+ }
418
+ let menuItem;
419
+ if (typeof item?.action === 'string') {
420
+ menuItem = a({
421
+ class: 'xin-menu-item',
422
+ href: item.action,
423
+ }, icon, options.localized ? span(localize(item.caption)) : span(item.caption), span(item.shortcut || ' '));
424
+ }
425
+ else {
426
+ menuItem = button({
427
+ class: 'xin-menu-item',
428
+ onClick: item.action,
429
+ }, icon, options.localized ? span(localize(item.caption)) : span(item.caption), span(item.shortcut || ' '));
430
+ }
431
+ menuItem.classList.toggle('xin-menu-item-checked', checked !== false);
432
+ if (item?.enabled && !item.enabled()) {
433
+ menuItem.setAttribute('disabled', '');
434
+ }
435
+ return menuItem;
436
+ };
437
+ export const createSubMenu = (item, options) => {
438
+ const checked = (item.checked && item.checked() && 'check') || false;
439
+ let icon = item?.icon || checked || span(' ');
440
+ if (typeof icon === 'string') {
441
+ icon = icons[icon]();
442
+ }
443
+ const submenuItem = button({
444
+ class: 'xin-menu-item',
445
+ disabled: !(!item.enabled || item.enabled()),
446
+ onClick(event) {
447
+ popMenu(Object.assign({}, options, {
448
+ menuItems: item.menuItems,
449
+ target: submenuItem,
450
+ submenuDepth: (options.submenuDepth || 0) + 1,
451
+ position: 'side',
452
+ }));
453
+ event.stopPropagation();
454
+ event.preventDefault();
455
+ },
456
+ }, icon, options.localized ? span(localize(item.caption)) : span(item.caption), icons.chevronRight({ style: { justifySelf: 'flex-end' } }));
457
+ return submenuItem;
458
+ };
459
+ export const createMenuItem = (item, options) => {
460
+ if (item === null) {
461
+ return span({ class: 'xin-menu-separator' });
462
+ }
463
+ else if (item?.action) {
464
+ return createMenuAction(item, options);
465
+ }
466
+ else {
467
+ return createSubMenu(item, options);
468
+ }
469
+ };
470
+ export const menu = (options) => {
471
+ const { target, width, menuItems } = options;
472
+ const hasIcons = menuItems.find((item) => item?.icon || item?.checked);
473
+ return div({
474
+ class: hasIcons ? 'xin-menu xin-menu-with-icons' : 'xin-menu',
475
+ onClick() {
476
+ removeLastMenu(0);
477
+ },
478
+ }, div({
479
+ style: {
480
+ minWidth: target.offsetWidth + 'px',
481
+ width: typeof width === 'number' ? `${width}px` : width,
482
+ },
483
+ onMousedown(event) {
484
+ event.preventDefault();
485
+ event.stopPropagation();
486
+ },
487
+ }, ...menuItems.map((item) => createMenuItem(item, options))));
488
+ };
489
+ let lastPopped;
490
+ const poppedMenus = [];
491
+ export const removeLastMenu = (depth = 0) => {
492
+ const toBeRemoved = poppedMenus.splice(depth);
493
+ for (const popped of toBeRemoved) {
494
+ popped.menu.remove();
495
+ }
496
+ lastPopped = toBeRemoved[0];
497
+ return depth > 0 ? poppedMenus[depth - 1] : undefined;
498
+ };
499
+ document.body.addEventListener('mousedown', (event) => {
500
+ if (event.target &&
501
+ !poppedMenus.find((popped) => popped.target.contains(event.target))) {
502
+ removeLastMenu(0);
503
+ }
504
+ });
505
+ document.body.addEventListener('keydown', (event) => {
506
+ if (event.key === 'Escape') {
507
+ removeLastMenu(0);
508
+ }
509
+ });
510
+ export const popMenu = (options) => {
511
+ options = Object.assign({ submenuDepth: 0 }, options);
512
+ const { target, position, submenuDepth } = options;
513
+ if (lastPopped && !document.body.contains(lastPopped?.menu)) {
514
+ lastPopped = undefined;
515
+ }
516
+ if (poppedMenus.length && !document.body.contains(poppedMenus[0].menu)) {
517
+ poppedMenus.splice(0);
518
+ }
519
+ if (submenuDepth === 0 && lastPopped?.target === target)
520
+ return;
521
+ const popped = removeLastMenu(submenuDepth);
522
+ if (lastPopped?.target === target)
523
+ return;
524
+ if (popped && popped.target === target) {
525
+ removeLastMenu();
526
+ return;
527
+ }
528
+ if (!options.menuItems?.length) {
529
+ return;
530
+ }
531
+ const content = menu(options);
532
+ const float = popFloat({
533
+ content,
534
+ target,
535
+ position,
536
+ });
537
+ float.remainOnScroll = 'remove';
538
+ poppedMenus.push({
539
+ target,
540
+ menu: float,
541
+ });
542
+ };
543
+ function findShortcutAction(items, event) {
544
+ for (const item of items) {
545
+ if (!item)
546
+ continue;
547
+ const { shortcut } = item;
548
+ const { menuItems } = item;
549
+ if (shortcut) {
550
+ if (matchShortcut(event, shortcut)) {
551
+ return item;
552
+ }
553
+ }
554
+ else if (menuItems) {
555
+ const foundAction = findShortcutAction(menuItems, event);
556
+ if (foundAction) {
557
+ return foundAction;
558
+ }
559
+ }
560
+ }
561
+ return undefined;
562
+ }
563
+ export class XinMenu extends Component {
564
+ menuItems = [];
565
+ menuWidth = 'auto';
566
+ localized = false;
567
+ showMenu = (event) => {
568
+ if (event.type === 'click' || event.code === 'Space') {
569
+ popMenu({
570
+ target: this.parts.trigger,
571
+ width: this.menuWidth,
572
+ localized: this.localized,
573
+ menuItems: this.menuItems,
574
+ });
575
+ event.stopPropagation();
576
+ event.preventDefault();
577
+ }
578
+ };
579
+ content = () => button({ tabindex: 0, part: 'trigger', onClick: this.showMenu }, xinSlot());
580
+ handleShortcut = async (event) => {
581
+ const menuAction = findShortcutAction(this.menuItems, event);
582
+ if (menuAction) {
583
+ if (menuAction.action instanceof Function) {
584
+ menuAction.action();
585
+ }
586
+ }
587
+ };
588
+ constructor() {
589
+ super();
590
+ this.initAttributes('menuWidth', 'localized', 'icon');
591
+ this.addEventListener('keydown', this.showMenu);
592
+ }
593
+ connectedCallback() {
594
+ super.connectedCallback();
595
+ document.addEventListener('keydown', this.handleShortcut, true);
596
+ }
597
+ disconnectedCallback() {
598
+ super.disconnectedCallback();
599
+ document.removeEventListener('keydown', this.handleShortcut);
600
+ }
601
+ }
602
+ export const xinMenu = XinMenu.elementCreator({
603
+ tag: 'xin-menu',
604
+ styleSpec: {
605
+ ':host': {
606
+ display: 'inline-block',
607
+ },
608
+ ':host button > xin-slot': {
609
+ display: 'flex',
610
+ alignItems: 'center',
611
+ gap: varDefault.xinMenuTriggerGap('10px'),
612
+ },
613
+ },
614
+ });
@@ -0,0 +1,106 @@
1
+ import { Component, ElementCreator } from 'tosijs';
2
+ interface NotificationSpec {
3
+ message: string;
4
+ type?: 'success' | 'info' | 'log' | 'warn' | 'error' | 'progress';
5
+ icon?: SVGElement | string;
6
+ duration?: number;
7
+ progress?: () => number;
8
+ close?: () => void;
9
+ }
10
+ type callback = () => void;
11
+ export declare class XinNotification extends Component {
12
+ private static singleton?;
13
+ static styleSpec: {
14
+ ':host': {
15
+ _notificationSpacing: number;
16
+ _notificationWidth: number;
17
+ _notificationPadding: string;
18
+ _notificationBg: string;
19
+ _notificationAccentColor: string;
20
+ _notificationTextColor: string;
21
+ _notificationIconSize: string;
22
+ _notificationButtonSize: number;
23
+ _notificationBorderWidth: string;
24
+ _notificationBorderRadius: string;
25
+ position: string;
26
+ left: number;
27
+ right: number;
28
+ bottom: number;
29
+ paddingBottom: string;
30
+ width: string;
31
+ display: string;
32
+ flexDirection: string;
33
+ margin: string;
34
+ gap: string;
35
+ maxHeight: string;
36
+ overflow: string;
37
+ boxShadow: string;
38
+ };
39
+ ':host *': {
40
+ color: string;
41
+ };
42
+ ':host .note': {
43
+ display: string;
44
+ background: string;
45
+ padding: string;
46
+ gridTemplateColumns: string;
47
+ gap: string;
48
+ alignItems: string;
49
+ borderRadius: string;
50
+ boxShadow: string;
51
+ borderColor: string;
52
+ borderWidth: string;
53
+ borderStyle: string;
54
+ transition: string;
55
+ transitionProperty: string;
56
+ zIndex: number;
57
+ };
58
+ ':host .note .icon': {
59
+ stroke: string;
60
+ };
61
+ ':host .note button': {
62
+ display: string;
63
+ lineHeight: string;
64
+ padding: number;
65
+ margin: number;
66
+ height: string;
67
+ width: string;
68
+ background: string;
69
+ alignItems: string;
70
+ justifyContent: string;
71
+ boxShadow: string;
72
+ border: string;
73
+ position: string;
74
+ };
75
+ ':host .note button:hover svg': {
76
+ stroke: string;
77
+ };
78
+ ':host .note button:active svg': {
79
+ borderRadius: number;
80
+ stroke: string;
81
+ background: string;
82
+ padding: string;
83
+ };
84
+ ':host .note svg': {
85
+ height: string;
86
+ width: string;
87
+ pointerEvents: string;
88
+ };
89
+ ':host .message': {
90
+ display: string;
91
+ flexDirection: string;
92
+ alignItems: string;
93
+ gap: string;
94
+ };
95
+ ':host .note.closing': {
96
+ opacity: number;
97
+ zIndex: number;
98
+ };
99
+ };
100
+ static removeNote(note: HTMLElement): void;
101
+ static post(spec: NotificationSpec | string): callback;
102
+ content: null;
103
+ }
104
+ export declare const xinNotification: ElementCreator<XinNotification>;
105
+ export declare function postNotification(spec: NotificationSpec | string): callback;
106
+ export {};