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/mapbox.js ADDED
@@ -0,0 +1,161 @@
1
+ /*#
2
+ # map
3
+
4
+ A [mapboxgl](https://docs.mapbox.com/mapbox-gl-js/api/) wrapper.
5
+
6
+ ```js
7
+ const pickStyle = preview.querySelector('select')
8
+ const mapbox = preview.querySelector('xin-map')
9
+ const here = preview.querySelector('button')
10
+
11
+ pickStyle.addEventListener('change', () => {
12
+ mapbox.mapStyle = pickStyle.value
13
+ })
14
+
15
+ function getUserGPSCoordinates() {
16
+ return new Promise((resolve) => {
17
+ // Check if geolocation is supported
18
+ if (!navigator.geolocation) {
19
+ console.log("Geolocation is not supported by this browser.");
20
+ resolve(null);
21
+ return;
22
+ }
23
+
24
+ // Request position with options
25
+ navigator.geolocation.getCurrentPosition(
26
+ // Success callback
27
+ (position) => {
28
+ resolve({
29
+ latitude: position.coords.latitude,
30
+ longitude: position.coords.longitude
31
+ });
32
+ },
33
+ // Error callback
34
+ (error) => {
35
+ console.log(`Error getting location: ${error.message}`);
36
+ resolve(null);
37
+ },
38
+ // Options
39
+ {
40
+ enableHighAccuracy: true, // Request high accuracy if available
41
+ timeout: 10000, // Time to wait for position (10 seconds)
42
+ maximumAge: 0 // Don't use cached position
43
+ }
44
+ );
45
+ });
46
+ }
47
+
48
+ here.addEventListener('click', async () => {
49
+ const location = await getUserGPSCoordinates()
50
+ if (location) {
51
+ mapbox.coords = `${location.latitude},${location.longitude},12`
52
+ }
53
+ })
54
+ ```
55
+ ```html
56
+ <!-- please don't abuse my mapbox token -->
57
+ <xin-map
58
+ style="width: 100%; height: 100%"
59
+ coords="14.0093606,120.995083,17"
60
+ token="pk.eyJ1IjoicG9kcGVyc29uIiwiYSI6ImNqc2JlbWU0bjA1ZmY0YW5ycHZod3VhbWcifQ.arvqfpOqMgFYkKgQ35UScA"
61
+ map-style="mapbox://styles/mapbox/streets-v12"
62
+ ></xin-map>
63
+ <select>
64
+ <option selected value="mapbox://styles/mapbox/streets-v12">Streets</option>
65
+ <option value="mapbox://styles/mapbox/satellite-v9">Satellite</option>
66
+ <option value="mapbox://styles/mapbox/light-v11">Light</option>
67
+ <option value="mapbox://styles/mapbox/dark-v11">Dark</option>
68
+ <option value="mapbox://styles/mapbox/outdoors-v12">Outdoors</option>
69
+ </select>
70
+ <button>
71
+ <xin-icon icon="mapPin"></xin-icon>
72
+ <span>Your Location</span>
73
+ </button>
74
+ ```
75
+ ```css
76
+ .preview button {
77
+ position: absolute;
78
+ right: 10px;
79
+ top: 10px;
80
+ display: flex;
81
+ align-items: center;
82
+ gap: 5px;
83
+ }
84
+
85
+ .preview select {
86
+ position: absolute;
87
+ bottom: 10px;
88
+ right: 10px;
89
+ }
90
+ ```
91
+
92
+ There's no need to learn new APIs or write wrappers, just access the element's `map` property
93
+ and [use the standard mapbox APIs directly](https://docs.mapbox.com/api/maps/styles/).
94
+ */
95
+ import { Component as WebComponent, elements } from 'xinjs';
96
+ import { styleSheet, scriptTag } from './via-tag';
97
+ const { div } = elements;
98
+ export class MapBox extends WebComponent {
99
+ coords = '65.01715565258993,25.48081004203459,12';
100
+ content = div({ style: { width: '100%', height: '100%' } });
101
+ get map() {
102
+ return this._map;
103
+ }
104
+ mapStyle = 'mapbox://styles/mapbox/streets-v12';
105
+ token = '';
106
+ static mapboxCSSAvailable;
107
+ static mapboxAvailable;
108
+ _map;
109
+ static styleSpec = {
110
+ ':host': {
111
+ display: 'inline-block',
112
+ position: 'relative',
113
+ width: '400px',
114
+ height: '400px',
115
+ textAlign: 'left',
116
+ },
117
+ };
118
+ constructor() {
119
+ super();
120
+ this.initAttributes('coords', 'token', 'mapStyle');
121
+ if (MapBox.mapboxCSSAvailable === undefined) {
122
+ MapBox.mapboxCSSAvailable = styleSheet('https://api.mapbox.com/mapbox-gl-js/v1.4.1/mapbox-gl.css').catch((e) => {
123
+ console.error('failed to load mapbox-gl.css', e);
124
+ });
125
+ MapBox.mapboxAvailable = scriptTag('https://api.mapbox.com/mapbox-gl-js/v1.4.1/mapbox-gl.js').catch((e) => {
126
+ console.error('failed to load mapbox-gl.js', e);
127
+ });
128
+ }
129
+ }
130
+ connectedCallback() {
131
+ super.connectedCallback();
132
+ if (!this.token) {
133
+ console.error('mapbox requires an access token which you can provide via the token attribute');
134
+ }
135
+ }
136
+ render() {
137
+ super.render();
138
+ if (!this.token) {
139
+ return;
140
+ }
141
+ const { div } = this.parts;
142
+ const [long, lat, zoom] = this.coords.split(',').map((x) => Number(x));
143
+ if (this.map) {
144
+ this.map.remove();
145
+ }
146
+ MapBox.mapboxAvailable.then(({ mapboxgl }) => {
147
+ console.log("%cmapbox may complain about missing css -- don't panic!", 'background: orange; color: black; padding: 0 5px;');
148
+ mapboxgl.accessToken = this.token;
149
+ this._map = new mapboxgl.Map({
150
+ container: div,
151
+ style: this.mapStyle,
152
+ zoom,
153
+ center: [lat, long],
154
+ });
155
+ this._map.on('render', () => this._map.resize());
156
+ });
157
+ }
158
+ }
159
+ export const mapBox = MapBox.elementCreator({
160
+ tag: 'xin-map',
161
+ });
@@ -0,0 +1,17 @@
1
+ import { Component, ElementCreator } from 'tosijs';
2
+ import { MarkedOptions } from 'marked';
3
+ export declare class MarkdownViewer extends Component {
4
+ src: string;
5
+ value: string;
6
+ content: null;
7
+ elements: boolean;
8
+ context: {
9
+ [key: string]: any;
10
+ };
11
+ options: MarkedOptions;
12
+ constructor();
13
+ connectedCallback(): void;
14
+ didRender: (() => void) | (() => Promise<void>);
15
+ render(): void;
16
+ }
17
+ export declare const markdownViewer: ElementCreator<MarkdownViewer>;
@@ -0,0 +1,173 @@
1
+ import { Component, xin } from 'xinjs';
2
+ import { marked } from 'marked';
3
+ /*#
4
+ # markdown
5
+
6
+ `<xin-md>` renders markdown using [marked](https://www.npmjs.com/package/marked).
7
+
8
+ `<xin-md>` renders [markdown](https://www.markdownguide.org/) anywhere, either using the
9
+ `src` attribute to load the file asynchronously, or rendering the text inside it.
10
+
11
+ ```html
12
+ <xin-md>
13
+ ## hello
14
+ world
15
+ </xin-md>
16
+ ```
17
+ ```css
18
+ xin-md {
19
+ display: block;
20
+ padding: var(--spacing);
21
+ }
22
+ ```
23
+
24
+ Note that, by default, `<xin-md>` will use its `textContent` (not its `innerHTML`) as its source.
25
+
26
+ ## rendering markdown from a url
27
+
28
+ Again, like an `<img>` tag, you can simply set a `<xin-md>`'s `src` attribute to a URL pointing
29
+ to markdown source and it will load it asynchronously and render it.
30
+
31
+ ```
32
+ <xin-md src="/path/to/file.md">
33
+ ```
34
+
35
+ ## setting its `value`
36
+
37
+ Or, just set the element's `value` and it will render it for you. You can try
38
+ this in the console, e.g.
39
+
40
+ ```
41
+ $('.preview xin-md').value = 'testing\n\n## this is a test'
42
+ ```
43
+
44
+ ## elements
45
+
46
+ `<xin-md>` also (optionally) allows the embedding of inline HTML elements without blocking markdown
47
+ rendering, so that you can embed specific elements while retaining markdown. You need to explicitly set
48
+ the `elements` property, and for markdown rendering not to be blocked, the html elements need to
49
+ start on a new line and not be indented. E.g.
50
+
51
+ ```html
52
+ <xin-md elements>
53
+ <form>
54
+ ### this is a form
55
+ <label>
56
+ fill in this field.
57
+ **It's important!**
58
+ <input>
59
+ </label>
60
+ </form>
61
+ </xin-md>
62
+ ```
63
+
64
+ In this case `<xin-md>` uses its `innerHTML` and not its `textContent`.
65
+
66
+ ## context and template variables
67
+
68
+ `<xin-md>` also supports **template** values. You need to provide data to the element in the form
69
+ of `context` (an arbitrary object, or a JSON string), and then embed the template text using
70
+ handlebars-style doubled curly braces, e.g. `{{path.to.value}}`.
71
+
72
+ If no value is found, the original text is passed through.
73
+
74
+ Finally, note that template substitution occurs *before* markdown transformation, which means you can
75
+ pass context data through to HTML elements.
76
+
77
+ ```html
78
+ <xin-md
79
+ elements
80
+ context='{"title": "template example", "foo": {"bar": 17}, "nested": "*work*: {{foo.bar}}"}'
81
+ >
82
+ ## {{title}}
83
+
84
+ The magic number is <input type="number" value={{foo.bar}}>
85
+
86
+ Oh, and nested templates {{nested}}.
87
+ </xin-md>
88
+ ```
89
+ */
90
+ function populate(basePath, source) {
91
+ if (source == null) {
92
+ source = '';
93
+ }
94
+ else if (typeof source !== 'string') {
95
+ source = String(source);
96
+ }
97
+ return source.replace(/\{\{([^}]+)\}\}/g, (original, prop) => {
98
+ const value = xin[`${basePath}${prop.startsWith('[') ? prop : '.' + prop}`];
99
+ return value === undefined ? original : populate(basePath, String(value));
100
+ });
101
+ }
102
+ export class MarkdownViewer extends Component {
103
+ src = '';
104
+ value = '';
105
+ content = null;
106
+ elements = false;
107
+ context = {};
108
+ constructor() {
109
+ super();
110
+ this.initAttributes('src', 'elements', 'context');
111
+ }
112
+ connectedCallback() {
113
+ super.connectedCallback();
114
+ if (this.src !== '') {
115
+ ;
116
+ (async () => {
117
+ const request = await fetch(this.src);
118
+ this.value = await request.text();
119
+ })();
120
+ }
121
+ else if (this.value === '') {
122
+ if (this.elements) {
123
+ this.value = this.innerHTML;
124
+ }
125
+ else {
126
+ this.value = this.textContent != null ? this.textContent : '';
127
+ }
128
+ }
129
+ }
130
+ didRender = () => {
131
+ /* do not care */
132
+ };
133
+ render() {
134
+ super.render();
135
+ xin[this.instanceId] =
136
+ typeof this.context === 'string' ? JSON.parse(this.context) : this.context;
137
+ const source = populate(this.instanceId, this.value);
138
+ if (this.elements) {
139
+ const chunks = source
140
+ .split('\n')
141
+ .reduce((chunks, line) => {
142
+ if (line.startsWith('<') || chunks.length === 0) {
143
+ chunks.push(line);
144
+ }
145
+ else {
146
+ const lastChunk = chunks[chunks.length - 1];
147
+ if (!lastChunk.startsWith('<') || !lastChunk.endsWith('>')) {
148
+ chunks[chunks.length - 1] += '\n' + line;
149
+ }
150
+ else {
151
+ chunks.push(line);
152
+ }
153
+ }
154
+ return chunks;
155
+ }, []);
156
+ this.innerHTML = chunks
157
+ .map((chunk) => chunk.startsWith('<') && chunk.endsWith('>')
158
+ ? chunk
159
+ : marked(chunk, { mangle: false, headerIds: false }))
160
+ .join('');
161
+ }
162
+ else {
163
+ this.innerHTML = marked(source, {
164
+ mangle: false,
165
+ headerIds: false,
166
+ });
167
+ }
168
+ this.didRender();
169
+ }
170
+ }
171
+ export const markdownViewer = MarkdownViewer.elementCreator({
172
+ tag: 'xin-md',
173
+ });
@@ -0,0 +1,9 @@
1
+ interface KeyboardEventLike {
2
+ key: string;
3
+ ctrlKey: boolean;
4
+ metaKey: boolean;
5
+ altKey: boolean;
6
+ shiftKey: boolean;
7
+ }
8
+ export declare const matchShortcut: (keystroke: KeyboardEventLike, shortcut: string) => boolean;
9
+ export {};
@@ -0,0 +1,13 @@
1
+ export const matchShortcut = (keystroke, shortcut) => {
2
+ shortcut = shortcut.toLocaleLowerCase();
3
+ const ctrlKey = !!shortcut.match(/\^|ctrl/);
4
+ const metaKey = !!shortcut.match(/⌘|meta/);
5
+ const altKey = !!shortcut.match(/⌥|⎇|alt|option/);
6
+ const shiftKey = !!shortcut.match(/⇧|shift/);
7
+ const baseKey = shortcut.slice(-1);
8
+ return (keystroke.key === baseKey &&
9
+ keystroke.metaKey === metaKey &&
10
+ keystroke.ctrlKey === ctrlKey &&
11
+ keystroke.altKey === altKey &&
12
+ keystroke.shiftKey === shiftKey);
13
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,194 @@
1
+ import { test, expect } from 'bun:test';
2
+ import { matchShortcut } from './match-shortcut';
3
+ test('simple shortcuts', () => {
4
+ expect(matchShortcut({
5
+ shiftKey: false,
6
+ ctrlKey: false,
7
+ altKey: false,
8
+ metaKey: false,
9
+ key: 'x',
10
+ }, 'X')).toBe(true);
11
+ expect(matchShortcut({
12
+ shiftKey: false,
13
+ ctrlKey: false,
14
+ altKey: false,
15
+ metaKey: false,
16
+ key: 'x',
17
+ }, 'x')).toBe(true);
18
+ expect(matchShortcut({
19
+ shiftKey: false,
20
+ ctrlKey: false,
21
+ altKey: false,
22
+ metaKey: false,
23
+ key: 'x',
24
+ }, 'y')).toBe(false);
25
+ });
26
+ test('ctrl keys', () => {
27
+ expect(matchShortcut({
28
+ shiftKey: false,
29
+ ctrlKey: true,
30
+ altKey: false,
31
+ metaKey: false,
32
+ key: 'x',
33
+ }, 'ctrl-x')).toBe(true);
34
+ expect(matchShortcut({
35
+ shiftKey: false,
36
+ ctrlKey: true,
37
+ altKey: false,
38
+ metaKey: false,
39
+ key: 'x',
40
+ }, '^X')).toBe(true);
41
+ expect(matchShortcut({
42
+ shiftKey: false,
43
+ ctrlKey: true,
44
+ altKey: false,
45
+ metaKey: false,
46
+ key: 'x',
47
+ }, 'x')).toBe(false);
48
+ expect(matchShortcut({
49
+ shiftKey: false,
50
+ ctrlKey: false,
51
+ altKey: false,
52
+ metaKey: false,
53
+ key: 'x',
54
+ }, '^X')).toBe(false);
55
+ expect(matchShortcut({
56
+ shiftKey: false,
57
+ ctrlKey: false,
58
+ altKey: false,
59
+ metaKey: true,
60
+ key: 'x',
61
+ }, 'ctrl-x')).toBe(false);
62
+ });
63
+ test('meta keys', () => {
64
+ expect(matchShortcut({
65
+ shiftKey: false,
66
+ ctrlKey: false,
67
+ altKey: false,
68
+ metaKey: true,
69
+ key: 'x',
70
+ }, 'meta-x')).toBe(true);
71
+ expect(matchShortcut({
72
+ shiftKey: false,
73
+ ctrlKey: false,
74
+ altKey: false,
75
+ metaKey: true,
76
+ key: 'x',
77
+ }, '⌘-X')).toBe(true);
78
+ expect(matchShortcut({
79
+ shiftKey: false,
80
+ ctrlKey: false,
81
+ altKey: false,
82
+ metaKey: true,
83
+ key: 'x',
84
+ }, 'x')).toBe(false);
85
+ expect(matchShortcut({
86
+ shiftKey: false,
87
+ ctrlKey: false,
88
+ altKey: false,
89
+ metaKey: false,
90
+ key: 'x',
91
+ }, '⌘-X')).toBe(false);
92
+ expect(matchShortcut({
93
+ shiftKey: false,
94
+ ctrlKey: true,
95
+ altKey: false,
96
+ metaKey: false,
97
+ key: 'x',
98
+ }, 'meta-x')).toBe(false);
99
+ });
100
+ test('alt keys', () => {
101
+ expect(matchShortcut({
102
+ shiftKey: false,
103
+ ctrlKey: false,
104
+ altKey: true,
105
+ metaKey: false,
106
+ key: 'x',
107
+ }, '⌥x')).toBe(true);
108
+ expect(matchShortcut({
109
+ shiftKey: false,
110
+ ctrlKey: false,
111
+ altKey: true,
112
+ metaKey: false,
113
+ key: 'x',
114
+ }, 'alt-X')).toBe(true);
115
+ expect(matchShortcut({
116
+ shiftKey: false,
117
+ ctrlKey: false,
118
+ altKey: true,
119
+ metaKey: false,
120
+ key: 'x',
121
+ }, '⎇-x')).toBe(true);
122
+ expect(matchShortcut({
123
+ shiftKey: false,
124
+ ctrlKey: false,
125
+ altKey: true,
126
+ metaKey: false,
127
+ key: 'x',
128
+ }, 'option-X')).toBe(true);
129
+ expect(matchShortcut({
130
+ shiftKey: false,
131
+ ctrlKey: false,
132
+ altKey: true,
133
+ metaKey: false,
134
+ key: 'x',
135
+ }, 'x')).toBe(false);
136
+ expect(matchShortcut({
137
+ shiftKey: false,
138
+ ctrlKey: false,
139
+ altKey: false,
140
+ metaKey: false,
141
+ key: 'x',
142
+ }, 'option-X')).toBe(false);
143
+ expect(matchShortcut({
144
+ shiftKey: false,
145
+ ctrlKey: true,
146
+ altKey: false,
147
+ metaKey: false,
148
+ key: 'x',
149
+ }, '⎇x')).toBe(false);
150
+ });
151
+ test('chorded modifiers', () => {
152
+ expect(matchShortcut({
153
+ shiftKey: false,
154
+ ctrlKey: false,
155
+ altKey: true,
156
+ metaKey: true,
157
+ key: 'x',
158
+ }, '⌘⌥x')).toBe(true);
159
+ expect(matchShortcut({
160
+ shiftKey: false,
161
+ ctrlKey: false,
162
+ altKey: true,
163
+ metaKey: true,
164
+ key: 'x',
165
+ }, '⌘⌥x')).toBe(true);
166
+ expect(matchShortcut({
167
+ shiftKey: false,
168
+ ctrlKey: false,
169
+ altKey: false,
170
+ metaKey: true,
171
+ key: 'x',
172
+ }, '⌘⌥x')).toBe(false);
173
+ expect(matchShortcut({
174
+ shiftKey: false,
175
+ ctrlKey: false,
176
+ altKey: false,
177
+ metaKey: true,
178
+ key: 'x',
179
+ }, 'alt-meta-x')).toBe(false);
180
+ expect(matchShortcut({
181
+ shiftKey: false,
182
+ ctrlKey: false,
183
+ altKey: true,
184
+ metaKey: false,
185
+ key: 'x',
186
+ }, '⌘⌥x')).toBe(false);
187
+ expect(matchShortcut({
188
+ shiftKey: true,
189
+ ctrlKey: false,
190
+ altKey: true,
191
+ metaKey: false,
192
+ key: 'x',
193
+ }, '⌘⌥x')).toBe(false);
194
+ });
package/dist/menu.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { Component, PartsMap } from 'tosijs';
2
+ import { FloatPosition } from './pop-float';
3
+ import { SvgIcon } from './icons';
4
+ export type ActionCallback = () => void | Promise<void>;
5
+ export interface MenuAction {
6
+ caption: string;
7
+ shortcut?: string;
8
+ checked?: () => boolean;
9
+ enabled?: () => boolean;
10
+ action: ActionCallback | string;
11
+ icon?: string | Element;
12
+ }
13
+ export interface SubMenu {
14
+ caption: string;
15
+ checked?: () => boolean;
16
+ enabled?: () => boolean;
17
+ menuItems: MenuItem[];
18
+ icon?: string | Element;
19
+ }
20
+ export type MenuSeparator = null;
21
+ export type MenuItem = MenuAction | SubMenu | MenuSeparator;
22
+ export declare const createMenuAction: (item: MenuAction, options: PopMenuOptions) => HTMLElement;
23
+ export declare const createSubMenu: (item: SubMenu, options: PopMenuOptions) => HTMLElement;
24
+ export declare const createMenuItem: (item: MenuItem, options: PopMenuOptions) => HTMLElement;
25
+ export declare const menu: (options: PopMenuOptions) => HTMLDivElement;
26
+ interface PoppedMenu {
27
+ target: HTMLElement;
28
+ menu: HTMLElement;
29
+ }
30
+ export declare const removeLastMenu: (depth?: number) => PoppedMenu | undefined;
31
+ export interface PopMenuOptions {
32
+ target: HTMLElement;
33
+ menuItems: MenuItem[];
34
+ width?: string | number;
35
+ position?: FloatPosition;
36
+ submenuDepth?: number;
37
+ submenuOffset?: {
38
+ x: number;
39
+ y: number;
40
+ };
41
+ localized?: boolean;
42
+ }
43
+ export declare const popMenu: (options: PopMenuOptions) => void;
44
+ interface XinMenuParts extends PartsMap {
45
+ trigger: HTMLButtonElement;
46
+ icon: SvgIcon;
47
+ }
48
+ export declare class XinMenu extends Component<XinMenuParts> {
49
+ menuItems: MenuItem[];
50
+ menuWidth: string;
51
+ localized: boolean;
52
+ showMenu: (event: Event) => void;
53
+ content: () => HTMLButtonElement;
54
+ handleShortcut: (event: KeyboardEvent) => Promise<void>;
55
+ constructor();
56
+ connectedCallback(): void;
57
+ disconnectedCallback(): void;
58
+ }
59
+ export declare const xinMenu: import("tosijs").ElementCreator<Component<PartsMap>>;
60
+ export {};