tosijs-ui 1.0.0 → 1.0.2

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 (94) hide show
  1. package/README.md +4 -2
  2. package/dist/iife.js +70 -60
  3. package/dist/iife.js.map +42 -42
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +15 -37
  6. package/dist/index.js.map +39 -39
  7. package/dist/version.d.ts +1 -1
  8. package/package.json +2 -2
  9. package/dist/ab-test.js +0 -116
  10. package/dist/babylon-3d.js +0 -292
  11. package/dist/bodymovin-player.js +0 -172
  12. package/dist/bp-loader.js +0 -26
  13. package/dist/carousel.js +0 -308
  14. package/dist/code-editor.js +0 -102
  15. package/dist/color-input.js +0 -112
  16. package/dist/data-table.js +0 -774
  17. package/dist/drag-and-drop.js +0 -386
  18. package/dist/editable-rect.js +0 -450
  19. package/dist/filter-builder.js +0 -468
  20. package/dist/float.js +0 -170
  21. package/dist/form.js +0 -466
  22. package/dist/gamepad.js +0 -115
  23. package/dist/icon-data.js +0 -308
  24. package/dist/icon-types.js +0 -1
  25. package/dist/icons.js +0 -374
  26. package/dist/index-iife.js +0 -4
  27. package/dist/live-example.js +0 -611
  28. package/dist/localize.js +0 -381
  29. package/dist/make-sorter.js +0 -119
  30. package/dist/make-sorter.test.d.ts +0 -1
  31. package/dist/make-sorter.test.js +0 -48
  32. package/dist/mapbox.js +0 -161
  33. package/dist/markdown-viewer.js +0 -173
  34. package/dist/match-shortcut.js +0 -13
  35. package/dist/match-shortcut.test.d.ts +0 -1
  36. package/dist/match-shortcut.test.js +0 -194
  37. package/dist/menu.js +0 -614
  38. package/dist/notifications.js +0 -308
  39. package/dist/password-strength.js +0 -302
  40. package/dist/playwright.config.d.ts +0 -9
  41. package/dist/playwright.config.js +0 -73
  42. package/dist/pop-float.js +0 -231
  43. package/dist/rating.js +0 -192
  44. package/dist/rich-text.js +0 -296
  45. package/dist/segmented.js +0 -298
  46. package/dist/select.js +0 -427
  47. package/dist/side-nav.js +0 -106
  48. package/dist/size-break.js +0 -118
  49. package/dist/sizer.js +0 -92
  50. package/dist/src/ab-test.d.ts +0 -14
  51. package/dist/src/babylon-3d.d.ts +0 -53
  52. package/dist/src/bodymovin-player.d.ts +0 -32
  53. package/dist/src/bp-loader.d.ts +0 -0
  54. package/dist/src/carousel.d.ts +0 -113
  55. package/dist/src/code-editor.d.ts +0 -27
  56. package/dist/src/color-input.d.ts +0 -41
  57. package/dist/src/data-table.d.ts +0 -79
  58. package/dist/src/drag-and-drop.d.ts +0 -2
  59. package/dist/src/editable-rect.d.ts +0 -97
  60. package/dist/src/filter-builder.d.ts +0 -64
  61. package/dist/src/float.d.ts +0 -18
  62. package/dist/src/form.d.ts +0 -68
  63. package/dist/src/gamepad.d.ts +0 -34
  64. package/dist/src/icon-data.d.ts +0 -309
  65. package/dist/src/icon-types.d.ts +0 -7
  66. package/dist/src/icons.d.ts +0 -17
  67. package/dist/src/index.d.ts +0 -37
  68. package/dist/src/live-example.d.ts +0 -51
  69. package/dist/src/localize.d.ts +0 -30
  70. package/dist/src/make-sorter.d.ts +0 -3
  71. package/dist/src/mapbox.d.ts +0 -24
  72. package/dist/src/markdown-viewer.d.ts +0 -15
  73. package/dist/src/match-shortcut.d.ts +0 -9
  74. package/dist/src/menu.d.ts +0 -60
  75. package/dist/src/notifications.d.ts +0 -106
  76. package/dist/src/password-strength.d.ts +0 -35
  77. package/dist/src/pop-float.d.ts +0 -10
  78. package/dist/src/rating.d.ts +0 -62
  79. package/dist/src/rich-text.d.ts +0 -28
  80. package/dist/src/segmented.d.ts +0 -80
  81. package/dist/src/select.d.ts +0 -43
  82. package/dist/src/side-nav.d.ts +0 -36
  83. package/dist/src/size-break.d.ts +0 -18
  84. package/dist/src/sizer.d.ts +0 -34
  85. package/dist/src/tab-selector.d.ts +0 -91
  86. package/dist/src/tag-list.d.ts +0 -37
  87. package/dist/src/track-drag.d.ts +0 -5
  88. package/dist/src/version.d.ts +0 -1
  89. package/dist/src/via-tag.d.ts +0 -2
  90. package/dist/tab-selector.js +0 -326
  91. package/dist/tag-list.js +0 -375
  92. package/dist/track-drag.js +0 -143
  93. package/dist/version.js +0 -1
  94. package/dist/via-tag.js +0 -102
package/dist/form.js DELETED
@@ -1,466 +0,0 @@
1
- /*#
2
- # forms
3
-
4
- `<xin-form>` and `<xin-field>` can be used to quickly create forms complete with
5
- [client-side validation](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#built-in_form_validation_examples).
6
-
7
- ```js
8
- const form = preview.querySelector('xin-form')
9
- preview.querySelector('.submit').addEventListener('click', form.submit)
10
- ```
11
- ```html
12
- <xin-form value='{"formInitializer": "initial value from form"}'>
13
- <h3 slot="header">Example Form Header</h3>
14
- <xin-field caption="Required field" key="required"></xin-field>
15
- <xin-field optional key="optional"><i>Optional</i> Field</xin-field>
16
- <xin-field key="text" type="text" placeholder="type it in here">Tell us a long story</xin-field>
17
- <xin-field caption="Zip Code" placeholder="12345 or 12345-6789" key="zipcode" pattern="\d{5}(-\d{4})?"></xin-field>
18
- <xin-field caption="Date" key="date" type="date"></xin-field>
19
- <xin-field caption="Number" key="number" type="number"></xin-field>
20
- <xin-field caption="Range" key="range" type="range" min="0" max="10"></xin-field>
21
- <xin-field key="boolean" type="checkbox">😃 <b>Agreed?!</b></xin-field>
22
- <xin-field key="color" type="color" value="pink">
23
- favorite color
24
- </xin-field>
25
- <xin-field key="select">
26
- Custom Field
27
- <select slot="input">
28
- <option>This</option>
29
- <option>That</option>
30
- <option>The Other</option>
31
- </select>
32
- </xin-field>
33
- <xin-field key="tags">
34
- Tag List
35
- <xin-tag-list editable slot="input" available-tags="pick me,no pick me"></xin-tag-list>
36
- </xin-field>
37
- <xin-field key="rating">
38
- Rate this form!
39
- <xin-rating slot="input"></xin-rating>
40
- </xin-field>
41
- <xin-field key="like">
42
- Do you like it?
43
- <xin-segmented
44
- choices="yes=Yes:thumbsUp,no=No:thumbsDown"
45
- slot="input"
46
- ></xin-segmented>
47
- </xin-field>
48
- <xin-field key="relationship">
49
- Relationship Status
50
- <xin-segmented
51
- style="--segmented-direction: column; --segmented-align-items: stretch"
52
- choices="couple=In a relationship,single=Single"
53
- other="It's complicated…"
54
- slot="input"
55
- ></xin-segmented>
56
- </xin-field>
57
- <xin-field key="amount" fixed-precision="2" type="number" prefix="$" suffix="(USD)">
58
- What's it worth?
59
- </xin-field>
60
- <xin-field key="valueInitializer" value="initial value from field">
61
- Initialized by field
62
- </xin-field>
63
- <xin-field key="formInitializer">
64
- Initialized by form
65
- </xin-field>
66
- <button slot="footer" class="submit">Submit</button>
67
- </xin-form>
68
- ```
69
- ```css
70
- .preview xin-form {
71
- height: 100%;
72
- }
73
-
74
- .preview ::part(header), .preview ::part(footer) {
75
- background: var(--inset-bg);
76
- justify-content: center;
77
- padding: calc(var(--spacing) * 0.5) var(--spacing);
78
- }
79
-
80
- .preview h3, .preview h4 {
81
- margin: 0;
82
- padding: 0;
83
- }
84
-
85
- .preview ::part(content) {
86
- padding: var(--spacing);
87
- gap: var(--spacing);
88
- background: var(--background);
89
- }
90
-
91
- .preview label {
92
- display: grid;
93
- grid-template-columns: 180px auto 100px;
94
- gap: var(--spacing);
95
- }
96
-
97
- .preview label [part="caption"] {
98
- text-align: right;
99
- }
100
-
101
- .preview label:has(:invalid:required)::after {
102
- content: '* required'
103
- }
104
-
105
- @media (max-width: 500px) {
106
- .preview label [part="caption"] {
107
- text-align: center;
108
- }
109
-
110
- .preview label {
111
- display: flex;
112
- flex-direction: column;
113
- position: relative;
114
- align-items: stretch;
115
- gap: calc(var(--spacing) * 0.5);
116
- }
117
-
118
- .preview label:has(:invalid:required)::after {
119
- position: absolute;
120
- top: 0;
121
- right: 0;
122
- content: '*'
123
- }
124
-
125
- .preview xin-field [part="field"],
126
- .preview xin-field [part="input"] > * {
127
- display: flex;
128
- justify-content: center;
129
- }
130
- }
131
-
132
- .preview :invalid {
133
- box-shadow: inset 0 0 0 2px #F008;
134
- }
135
- ```
136
-
137
- ## `<xin-form>`
138
-
139
- `<xin-form>` prevents the default form behavior when a `submit` event is triggered and instead validates the
140
- form contents (generating feedback if desired) and calls its `submitCallback(value: {[key: string]: any}, isValid: boolean): void`
141
- method.
142
-
143
- `<xin-form>` offers a `fields` proxy that allows values stored in the form to be updated. Any changes will trigger a `change`
144
- event on the `<xin-form>` (in addition to any events fired by form fields).
145
-
146
- `<xin-form>` instances have `value` and `isValid` properties you can access any time. Note that `isValid` is computed
147
- and triggers form validation.
148
-
149
- `<xin-form>` has `header` and `footer` `<slot>`s in addition to default `<slot>`, which is tucked inside a `<form>` element.
150
-
151
- ## `<xin-field>`
152
-
153
- `<xin-field>` is a simple web-component with no shadowDOM that combines an `<input>` field wrapped with a `<label>`. Any
154
- content of the custom-element will become the `caption` or you can simply set the `caption` attribute.
155
-
156
- You can replace the default `<input>` field by adding an element to the slot `input` (it's a `xinSlot`) whereupon
157
- the `value` of that element will be used instead of the built-in `<input>`. (The `<input>` is retained and
158
- is used to drive form-validation.)
159
-
160
- `<xin-field>` supports the following attributes:
161
-
162
- - `caption` labels the field
163
- - `key` determines the form property the field will populate
164
- - `type` determines the data-type: '' | 'checkbox' | 'number' | 'range' | 'date' | 'text' | 'color'
165
- - `optional` turns off the `required` attribute (fields are required by default)
166
- - `pattern` is an (optional) regex pattern
167
- - `placeholder` is an (optional) placeholder
168
-
169
- The `text` type populates the `input` slot with a `<textarea>` element.
170
-
171
- The `color` type populates the `input` slot with a `<xin-color>` element (and thus supports colors with alpha values).
172
-
173
- <xin-css-var-editor element-selector="xin-field" target-selector=".preview"></xin-css-var-editor>
174
- */
175
- import { Component as XinComponent, elements, varDefault, } from 'xinjs';
176
- import { colorInput } from './color-input';
177
- const { form, slot, xinSlot, label, input, span } = elements;
178
- function attr(element, name, value) {
179
- if (value !== '' && value !== false) {
180
- element.setAttribute(name, value);
181
- }
182
- else {
183
- element.removeAttribute(name);
184
- }
185
- }
186
- function getInputValue(input) {
187
- switch (input.type) {
188
- case 'checkbox':
189
- return input.checked;
190
- case 'radio': {
191
- const picked = input.parentElement?.querySelector(`input[type="radio"][name="${input.name}"]:checked`);
192
- return picked ? picked.value : null;
193
- }
194
- case 'range':
195
- case 'number':
196
- return Number(input.value);
197
- default:
198
- return Array.isArray(input.value) && input.value.length === 0
199
- ? null
200
- : input.value;
201
- }
202
- }
203
- function setElementValue(input, value) {
204
- if (!(input instanceof HTMLElement)) {
205
- // do nothing
206
- }
207
- else if (input instanceof HTMLInputElement) {
208
- switch (input.type) {
209
- case 'checkbox':
210
- input.checked = value;
211
- break;
212
- case 'radio':
213
- input.checked = value === input.value;
214
- break;
215
- default:
216
- input.value = String(value || '');
217
- }
218
- }
219
- else {
220
- if (value != null || input.value != null) {
221
- ;
222
- input.value = String(value || '');
223
- }
224
- }
225
- }
226
- export class XinField extends XinComponent {
227
- caption = '';
228
- key = '';
229
- type = '';
230
- optional = false;
231
- pattern = '';
232
- placeholder = '';
233
- min = '';
234
- max = '';
235
- step = '';
236
- fixedPrecision = -1;
237
- value = null;
238
- content = label(xinSlot({ part: 'caption' }), span({ part: 'field' }, xinSlot({ part: 'input', name: 'input' }), input({ part: 'valueHolder' })));
239
- constructor() {
240
- super();
241
- this.initAttributes('caption', 'key', 'type', 'optional', 'pattern', 'placeholder', 'min', 'max', 'step', 'fixedPrecision', 'prefix', 'suffix');
242
- }
243
- valueChanged = false;
244
- handleChange = () => {
245
- const { input, valueHolder } = this.parts;
246
- const inputElement = (input.children[0] || valueHolder);
247
- if (inputElement !== valueHolder) {
248
- valueHolder.value = inputElement.value;
249
- }
250
- this.value = getInputValue(inputElement);
251
- this.valueChanged = true;
252
- const form = this.closest('xin-form');
253
- if (form && this.key !== '') {
254
- switch (this.type) {
255
- case 'checkbox':
256
- form.fields[this.key] = inputElement.checked;
257
- break;
258
- case 'number':
259
- case 'range':
260
- if (this.fixedPrecision > -1) {
261
- inputElement.value = Number(inputElement.value).toFixed(this.fixedPrecision);
262
- form.fields[this.key] = Number(inputElement.value);
263
- }
264
- else {
265
- form.fields[this.key] = Number(inputElement.value);
266
- }
267
- break;
268
- default:
269
- form.fields[this.key] = inputElement.value;
270
- }
271
- }
272
- };
273
- initialize(form) {
274
- const initialValue = form.fields[this.key] !== undefined ? form.fields[this.key] : this.value;
275
- if (initialValue != null && initialValue !== '') {
276
- if (form.fields[this.key] == null)
277
- form.fields[this.key] = initialValue;
278
- this.value = initialValue;
279
- }
280
- }
281
- connectedCallback() {
282
- super.connectedCallback();
283
- const { input, valueHolder } = this.parts;
284
- const form = this.closest(XinForm.tagName);
285
- if (form instanceof XinForm) {
286
- this.initialize(form);
287
- }
288
- valueHolder.addEventListener('change', this.handleChange);
289
- input.addEventListener('change', this.handleChange, true);
290
- }
291
- render() {
292
- if (this.valueChanged) {
293
- this.valueChanged = false;
294
- return;
295
- }
296
- const { input, caption, valueHolder, field } = this.parts;
297
- if (caption.textContent?.trim() === '') {
298
- caption.append(this.caption !== '' ? this.caption : this.key);
299
- }
300
- if (this.type === 'text') {
301
- input.textContent = '';
302
- const textarea = elements.textarea({ value: this.value });
303
- if (this.placeholder) {
304
- textarea.setAttribute('placeholder', this.placeholder);
305
- }
306
- input.append(textarea);
307
- }
308
- else if (this.type === 'color') {
309
- input.textContent = '';
310
- input.append(colorInput({ value: this.value }));
311
- }
312
- else if (input.children.length === 0) {
313
- attr(valueHolder, 'placeholder', this.placeholder);
314
- attr(valueHolder, 'type', this.type);
315
- attr(valueHolder, 'pattern', this.pattern);
316
- attr(valueHolder, 'min', this.min);
317
- attr(valueHolder, 'max', this.max);
318
- attr(valueHolder, 'step', this.step);
319
- }
320
- setElementValue(valueHolder, this.value);
321
- setElementValue(input.children[0], this.value);
322
- this.prefix
323
- ? field.setAttribute('prefix', this.prefix)
324
- : field.removeAttribute('prefix');
325
- this.suffix
326
- ? field.setAttribute('suffix', this.suffix)
327
- : field.removeAttribute('suffix');
328
- valueHolder.classList.toggle('hidden', input.children.length > 0);
329
- if (input.children.length > 0) {
330
- valueHolder.setAttribute('tabindex', '-1');
331
- }
332
- else {
333
- valueHolder.removeAttribute('tabindex');
334
- }
335
- input.style.display = input.children.length === 0 ? 'none' : '';
336
- attr(valueHolder, 'required', !this.optional);
337
- }
338
- }
339
- export class XinForm extends XinComponent {
340
- context = {};
341
- value = {};
342
- get isValid() {
343
- const widgets = [...this.querySelectorAll('*')].filter((widget) => widget.required !== undefined);
344
- return widgets.find((widget) => !widget.reportValidity()) === undefined;
345
- }
346
- static styleSpec = {
347
- ':host': {
348
- display: 'flex',
349
- flexDirection: 'column',
350
- },
351
- ':host::part(header), :host::part(footer)': {
352
- display: 'flex',
353
- },
354
- ':host::part(content)': {
355
- display: 'flex',
356
- flexDirection: 'column',
357
- overflow: 'hidden auto',
358
- height: '100%',
359
- width: '100%',
360
- position: 'relative',
361
- boxSizing: 'border-box',
362
- },
363
- ':host form': {
364
- display: 'flex',
365
- flex: '1 1 auto',
366
- position: 'relative',
367
- overflow: 'hidden',
368
- },
369
- };
370
- content = [
371
- slot({ part: 'header', name: 'header' }),
372
- form({ part: 'form' }, slot({ part: 'content' })),
373
- slot({ part: 'footer', name: 'footer' }),
374
- ];
375
- getField = (key) => {
376
- return this.querySelector(`xin-field[key="${key}"]`);
377
- };
378
- get fields() {
379
- if (typeof this.value === 'string') {
380
- try {
381
- this.value = JSON.parse(this.value);
382
- }
383
- catch (e) {
384
- console.log('<xin-form> could not use its value, expects valid JSON');
385
- this.value = {};
386
- }
387
- }
388
- const { getField } = this;
389
- const dispatch = this.dispatchEvent.bind(this);
390
- return new Proxy(this.value, {
391
- get(target, prop) {
392
- return target[prop];
393
- },
394
- set(target, prop, newValue) {
395
- if (target[prop] !== newValue) {
396
- target[prop] = newValue;
397
- const field = getField(prop);
398
- if (field) {
399
- field.value = newValue;
400
- }
401
- dispatch(new Event('change'));
402
- }
403
- return true;
404
- },
405
- });
406
- }
407
- set fields(values) {
408
- const fields = [...this.querySelectorAll(XinField.tagName)];
409
- for (const field of fields) {
410
- field.value = values[field.key];
411
- }
412
- }
413
- submit = () => {
414
- this.parts.form.dispatchEvent(new Event('submit'));
415
- };
416
- handleSubmit = (event) => {
417
- event.preventDefault();
418
- event.stopPropagation();
419
- this.submitCallback(this.value, this.isValid);
420
- };
421
- submitCallback = (value, isValid) => {
422
- console.log('override submitCallback to handle this data', {
423
- value,
424
- isValid,
425
- });
426
- };
427
- connectedCallback() {
428
- super.connectedCallback();
429
- const { form } = this.parts;
430
- form.addEventListener('submit', this.handleSubmit);
431
- }
432
- }
433
- export const xinField = XinField.elementCreator({
434
- tag: 'xin-field',
435
- styleSpec: {
436
- ':host [part="field"]': {
437
- position: 'relative',
438
- display: 'flex',
439
- alignItems: 'center',
440
- gap: varDefault.prefixSuffixGap('8px'),
441
- },
442
- ':host [part="field"][prefix]::before': {
443
- content: 'attr(prefix)',
444
- },
445
- ':host [part="field"][suffix]::after': {
446
- content: 'attr(suffix)',
447
- },
448
- ':host [part="field"] > *, :host [part="input"] > *': {
449
- width: '100%',
450
- },
451
- ':host textarea': {
452
- resize: 'none',
453
- },
454
- ':host input[type="checkbox"]': {
455
- width: 'fit-content',
456
- },
457
- ':host .hidden': {
458
- position: 'absolute',
459
- pointerEvents: 'none',
460
- opacity: 0,
461
- },
462
- },
463
- });
464
- export const xinForm = XinForm.elementCreator({
465
- tag: 'xin-form',
466
- });
package/dist/gamepad.js DELETED
@@ -1,115 +0,0 @@
1
- /*#
2
- # gamepads
3
-
4
- A couple of utility functions for dealing with gamepads and XRInputs.
5
-
6
- `gamepadState()` gives you a condensed version of active gamepad state
7
-
8
- `gamepadText()` provides the above in minimal text form for debugging
9
-
10
- ```js
11
- const { elements } = xinjs
12
- const { gamepadText } = xinjsui
13
-
14
- const pre = elements.pre()
15
- preview.append(pre)
16
-
17
- const interval = setInterval(() => {
18
- if (!pre.closest('body')) {
19
- clearInterval(interval)
20
- } else {
21
- pre.textContent = gamepadText()
22
- }
23
- }, 100)
24
- ```
25
-
26
- ## XRInput Devices
27
-
28
- > This is experimental, the API is changing and stuff doesn't work. Hopefully it
29
- > will become a lot more generally useful once Apple's VisionPro comes out.
30
-
31
- `xrControllers(babylonjsXRHelper)` returns an `XinXRControllerMap` structure that tries to
32
- conveniently render the current state of XR controls. (The babylonjs API for this is horrific!)
33
-
34
- `xrControllerText(controllerMap)` renders the map output by the above in a compact form
35
- which is useful when debugging.
36
- */
37
- export function gamepadState() {
38
- const gamepads = navigator
39
- .getGamepads()
40
- .filter((p) => p !== null);
41
- return gamepads.map((p) => {
42
- const { id, axes, buttons } = p;
43
- return {
44
- id,
45
- axes,
46
- buttons: buttons
47
- .map((button, index) => {
48
- const { pressed, value } = button;
49
- return {
50
- index,
51
- pressed,
52
- value,
53
- };
54
- })
55
- .filter((b) => b.pressed || b.value !== 0)
56
- .reduce((map, button) => {
57
- map[button.index] = button.value;
58
- return map;
59
- }, {}),
60
- };
61
- });
62
- }
63
- export function gamepadText() {
64
- const state = gamepadState();
65
- return state.length === 0
66
- ? 'no active gamepads'
67
- : state
68
- .map(({ id, axes, buttons }) => {
69
- const axesText = axes.map((a) => a.toFixed(2)).join(' ');
70
- const buttonText = Object.keys(buttons)
71
- .map((key) => `[${key}](${buttons[Number(key)].toFixed(2)})`)
72
- .join(' ');
73
- return `${id}\n${axesText}\n${buttonText}`;
74
- })
75
- .join('\n');
76
- }
77
- export function xrControllers(xrHelper) {
78
- const controllers = {};
79
- xrHelper.input.onControllerAddedObservable.add((controller) => {
80
- controller.onMotionControllerInitObservable.add((mc) => {
81
- const state = {};
82
- const componentIds = mc.getComponentIds();
83
- componentIds.forEach((componentId) => {
84
- const component = mc.getComponent(componentId);
85
- state[componentId] = { pressed: component.pressed };
86
- component.onButtonStateChangedObservable.add(() => {
87
- state[componentId].pressed = component.pressed;
88
- });
89
- // TODO does this work?! inquiring minds…
90
- if (component.onAxisValueChangedObservable) {
91
- state[componentId].axes = [];
92
- component.onAxisValueChangedObservable.add((axes) => {
93
- state[componentId].axes = axes;
94
- });
95
- }
96
- });
97
- controllers[mc.handedness] = state;
98
- });
99
- });
100
- return controllers;
101
- }
102
- export function xrControllersText(controllers) {
103
- if (controllers === undefined || Object.keys(controllers).length === 0) {
104
- return 'no xr inputs';
105
- }
106
- return Object.keys(controllers)
107
- .map((controllerId) => {
108
- const state = controllers[controllerId];
109
- const buttonText = Object.keys(state)
110
- .filter((componentId) => state[componentId].pressed)
111
- .join(' ');
112
- return `${controllerId}\n${buttonText}`;
113
- })
114
- .join('\n');
115
- }