@haiyue/ui 0.1.2 → 0.2.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 (110) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +27 -0
  3. package/README.md +643 -2
  4. package/dist/adapters/web/controls.d.ts +19 -0
  5. package/dist/adapters/web/controls.js +307 -0
  6. package/dist/adapters/web/dom.d.ts +10 -0
  7. package/dist/adapters/web/dom.js +98 -0
  8. package/dist/adapters/web/index.d.ts +3 -0
  9. package/dist/adapters/web/index.js +3 -0
  10. package/dist/adapters/web/renderer.d.ts +18 -0
  11. package/dist/adapters/web/renderer.js +553 -0
  12. package/dist/adapters/web/styles.d.ts +3 -0
  13. package/dist/adapters/web/styles.js +83 -0
  14. package/dist/adapters/web/types.d.ts +46 -0
  15. package/dist/adapters/web/types.js +2 -0
  16. package/dist/affix.d.ts +35 -0
  17. package/dist/affix.js +137 -0
  18. package/dist/badge.d.ts +45 -0
  19. package/dist/badge.js +168 -0
  20. package/dist/breadcrumb.d.ts +37 -0
  21. package/dist/breadcrumb.js +210 -0
  22. package/dist/button-group.d.ts +12 -0
  23. package/dist/button-group.js +72 -0
  24. package/dist/button.d.ts +15 -1
  25. package/dist/button.js +101 -12
  26. package/dist/carousel.d.ts +92 -0
  27. package/dist/carousel.js +509 -0
  28. package/dist/cascader.d.ts +65 -0
  29. package/dist/cascader.js +479 -0
  30. package/dist/collapse.d.ts +68 -0
  31. package/dist/collapse.js +348 -0
  32. package/dist/core/actions.d.ts +32 -0
  33. package/dist/core/actions.js +145 -0
  34. package/dist/core/data.d.ts +4 -0
  35. package/dist/core/data.js +27 -0
  36. package/dist/core/index.d.ts +7 -0
  37. package/dist/core/index.js +6 -0
  38. package/dist/core/platform-view.d.ts +17 -0
  39. package/dist/core/platform-view.js +84 -0
  40. package/dist/core/selection.d.ts +14 -0
  41. package/dist/core/selection.js +93 -0
  42. package/dist/core/text.d.ts +31 -0
  43. package/dist/core/text.js +154 -0
  44. package/dist/core/types.d.ts +269 -0
  45. package/dist/core/types.js +2 -0
  46. package/dist/core/validation.d.ts +24 -0
  47. package/dist/core/validation.js +243 -0
  48. package/dist/date-picker.d.ts +60 -0
  49. package/dist/date-picker.js +355 -0
  50. package/dist/dialog.d.ts +4 -0
  51. package/dist/dialog.js +23 -5
  52. package/dist/divider.d.ts +33 -0
  53. package/dist/divider.js +113 -0
  54. package/dist/draggable.d.ts +78 -0
  55. package/dist/draggable.js +255 -0
  56. package/dist/index.d.ts +53 -1
  57. package/dist/index.js +78 -0
  58. package/dist/input.d.ts +30 -1
  59. package/dist/input.js +166 -54
  60. package/dist/internal/qrcodegen.d.ts +94 -0
  61. package/dist/internal/qrcodegen.js +856 -0
  62. package/dist/masonry.d.ts +36 -0
  63. package/dist/masonry.js +171 -0
  64. package/dist/modal.d.ts +73 -0
  65. package/dist/modal.js +361 -0
  66. package/dist/portable/icons.d.ts +5 -0
  67. package/dist/portable/icons.js +24 -0
  68. package/dist/portable/index.d.ts +7 -0
  69. package/dist/portable/index.js +5 -0
  70. package/dist/portable/page.d.ts +38 -0
  71. package/dist/portable/page.js +361 -0
  72. package/dist/portable/responsive.d.ts +20 -0
  73. package/dist/portable/responsive.js +15 -0
  74. package/dist/portable/theme.d.ts +24 -0
  75. package/dist/portable/theme.js +60 -0
  76. package/dist/portable/tree.d.ts +10 -0
  77. package/dist/portable/tree.js +96 -0
  78. package/dist/qr-code.d.ts +62 -0
  79. package/dist/qr-code.js +224 -0
  80. package/dist/range.d.ts +3 -0
  81. package/dist/range.js +16 -5
  82. package/dist/rate.d.ts +46 -0
  83. package/dist/rate.js +193 -0
  84. package/dist/resizable.d.ts +97 -0
  85. package/dist/resizable.js +426 -0
  86. package/dist/skeleton.d.ts +41 -0
  87. package/dist/skeleton.js +141 -0
  88. package/dist/steps.d.ts +45 -0
  89. package/dist/steps.js +203 -0
  90. package/dist/table.d.ts +106 -0
  91. package/dist/table.js +689 -0
  92. package/dist/tabs.d.ts +3 -0
  93. package/dist/tabs.js +11 -1
  94. package/dist/tag.d.ts +55 -0
  95. package/dist/tag.js +225 -0
  96. package/dist/time-picker.d.ts +62 -0
  97. package/dist/time-picker.js +298 -0
  98. package/dist/timeline.d.ts +48 -0
  99. package/dist/timeline.js +201 -0
  100. package/dist/toast.d.ts +55 -0
  101. package/dist/toast.js +224 -0
  102. package/dist/tour.d.ts +89 -0
  103. package/dist/tour.js +306 -0
  104. package/dist/virtual-keyboard.d.ts +95 -0
  105. package/dist/virtual-keyboard.js +432 -0
  106. package/dist/virtual-mouse.d.ts +82 -0
  107. package/dist/virtual-mouse.js +358 -0
  108. package/dist/watermark.d.ts +44 -0
  109. package/dist/watermark.js +148 -0
  110. package/package.json +132 -4
@@ -0,0 +1,95 @@
1
+ export type HYVirtualKeyboardSource = 'pointer' | 'keyboard' | 'physical' | 'api';
2
+ export type HYVirtualKeyboardMode = 'qwerty' | 'numeric';
3
+ export type HYVirtualKeyboardAction = 'character' | 'backspace' | 'delete' | 'enter' | 'tab' | 'space' | 'shift' | 'caps-lock' | 'escape' | 'arrow-left' | 'arrow-right' | 'arrow-up' | 'arrow-down' | 'home' | 'end';
4
+ export interface HYVirtualKeyboardKey {
5
+ readonly code: string;
6
+ readonly label: string;
7
+ readonly value?: string;
8
+ readonly shiftLabel?: string;
9
+ readonly shiftValue?: string;
10
+ readonly action?: HYVirtualKeyboardAction;
11
+ readonly width?: number;
12
+ readonly disabled?: boolean;
13
+ }
14
+ export type HYVirtualKeyboardLayout = readonly (readonly HYVirtualKeyboardKey[])[];
15
+ export interface HYVirtualKeyboardKeyDetail {
16
+ readonly code: string;
17
+ readonly key: string;
18
+ readonly action: HYVirtualKeyboardAction;
19
+ readonly source: HYVirtualKeyboardSource;
20
+ readonly pressedKeys: readonly string[];
21
+ readonly shiftKey: boolean;
22
+ readonly capsLock: boolean;
23
+ }
24
+ export interface HYVirtualKeyboardInputDetail extends HYVirtualKeyboardKeyDetail {
25
+ readonly value: string;
26
+ readonly inputType: string;
27
+ }
28
+ export interface HYVirtualKeyboardTapOptions {
29
+ readonly holdDuration?: number;
30
+ }
31
+ export interface HYVirtualKeyboardTypeOptions {
32
+ readonly interval?: number;
33
+ }
34
+ export declare const HY_VIRTUAL_KEYBOARD_QWERTY_LAYOUT: HYVirtualKeyboardLayout;
35
+ export declare const HY_VIRTUAL_KEYBOARD_NUMERIC_LAYOUT: HYVirtualKeyboardLayout;
36
+ /** Themeable on-screen keyboard with programmable key actions and optional input binding. */
37
+ export declare class HYVirtualKeyboard extends HTMLElement {
38
+ private readonly _keyboard;
39
+ private _layout;
40
+ private _layoutAssigned;
41
+ private _target;
42
+ private readonly _pressed;
43
+ private readonly _pressedValues;
44
+ private readonly _buttons;
45
+ private _shift;
46
+ private _capsLock;
47
+ private _listeners;
48
+ private _operation;
49
+ static get observedAttributes(): string[];
50
+ constructor();
51
+ connectedCallback(): void;
52
+ disconnectedCallback(): void;
53
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
54
+ get layout(): HYVirtualKeyboardLayout;
55
+ set layout(value: HYVirtualKeyboardLayout);
56
+ get mode(): HYVirtualKeyboardMode;
57
+ set mode(value: HYVirtualKeyboardMode);
58
+ get target(): HTMLInputElement | HTMLTextAreaElement | null;
59
+ set target(value: HTMLInputElement | HTMLTextAreaElement | null);
60
+ get disabled(): boolean;
61
+ set disabled(value: boolean);
62
+ get physicalSync(): boolean;
63
+ set physicalSync(value: boolean);
64
+ get shiftActive(): boolean;
65
+ get capsLock(): boolean;
66
+ get pressedKeys(): readonly string[];
67
+ press(code: string, source?: HYVirtualKeyboardSource): void;
68
+ release(code: string, source?: HYVirtualKeyboardSource): void;
69
+ tap(code: string, options?: HYVirtualKeyboardTapOptions): Promise<void>;
70
+ type(text: string, options?: HYVirtualKeyboardTypeOptions): Promise<void>;
71
+ clear(): void;
72
+ private _readLayoutAttribute;
73
+ private _applyModeLayout;
74
+ private _render;
75
+ private _syncLabel;
76
+ private _syncVisuals;
77
+ private _press;
78
+ private _release;
79
+ private _applyKey;
80
+ private _insertText;
81
+ private _deleteBackward;
82
+ private _deleteForward;
83
+ private _moveCaret;
84
+ private _moveCaretVertical;
85
+ private _setCaret;
86
+ private _dispatchTargetInput;
87
+ private _emitInput;
88
+ private _dispatchKeyEvent;
89
+ private _resolveKey;
90
+ private _findKey;
91
+ private _findCharacterKey;
92
+ private _connectPhysicalKeyboard;
93
+ private _disconnectPhysicalKeyboard;
94
+ }
95
+ export declare function defineVirtualKeyboardComponents(): void;
@@ -0,0 +1,432 @@
1
+ const LETTERS = 'QWERTYUIOPASDFGHJKLZXCVBNM';
2
+ const DIGITS = [
3
+ ['Digit1', '1', '!'], ['Digit2', '2', '@'], ['Digit3', '3', '#'], ['Digit4', '4', '$'], ['Digit5', '5', '%'],
4
+ ['Digit6', '6', '^'], ['Digit7', '7', '&'], ['Digit8', '8', '*'], ['Digit9', '9', '('], ['Digit0', '0', ')'],
5
+ ];
6
+ const letter = (value) => ({ code: `Key${value}`, label: value, value: value.toLowerCase(), shiftValue: value });
7
+ const action = (code, label, actionName, width = 1) => ({ code, label, action: actionName, width });
8
+ export const HY_VIRTUAL_KEYBOARD_QWERTY_LAYOUT = [
9
+ [
10
+ ...DIGITS.map(([code, value, shifted]) => ({ code, label: value, value, shiftLabel: shifted, shiftValue: shifted })),
11
+ action('Backspace', 'Backspace', 'backspace', 1.8),
12
+ ],
13
+ [action('Tab', 'Tab', 'tab', 1.4), ...'QWERTYUIOP'.split('').map(letter)],
14
+ [action('CapsLock', 'Caps', 'caps-lock', 1.7), ...'ASDFGHJKL'.split('').map(letter), action('Enter', 'Enter', 'enter', 1.7)],
15
+ [action('ShiftLeft', 'Shift', 'shift', 2.1), ...'ZXCVBNM'.split('').map(letter), action('Delete', 'Delete', 'delete', 1.35)],
16
+ [action('Escape', 'Esc', 'escape', 1.1), action('Space', 'Space', 'space', 5), action('Home', 'Home', 'home', 1.1), action('ArrowLeft', '←', 'arrow-left'), action('ArrowUp', '↑', 'arrow-up'), action('ArrowDown', '↓', 'arrow-down'), action('ArrowRight', '→', 'arrow-right'), action('End', 'End', 'end', 1.1)],
17
+ ];
18
+ const numeric = (value) => ({ code: `Numpad${value}`, label: value, value });
19
+ export const HY_VIRTUAL_KEYBOARD_NUMERIC_LAYOUT = [
20
+ [numeric('7'), numeric('8'), numeric('9'), action('Backspace', '⌫', 'backspace')],
21
+ [numeric('4'), numeric('5'), numeric('6'), action('Delete', 'Delete', 'delete')],
22
+ [numeric('1'), numeric('2'), numeric('3'), action('NumpadEnter', 'Enter', 'enter')],
23
+ [{ ...numeric('0'), width: 2 }, { code: 'NumpadDecimal', label: '.', value: '.' }, { code: 'NumpadSubtract', label: '−', value: '-' }],
24
+ ];
25
+ function normalizeLayout(value) {
26
+ if (!Array.isArray(value))
27
+ return HY_VIRTUAL_KEYBOARD_QWERTY_LAYOUT;
28
+ const rows = value.flatMap(row => {
29
+ if (!Array.isArray(row))
30
+ return [];
31
+ const keys = row.flatMap(raw => {
32
+ if (!raw || typeof raw !== 'object')
33
+ return [];
34
+ const item = raw;
35
+ if (item.code == null || item.label == null)
36
+ return [];
37
+ const actionName = String(item.action ?? 'character');
38
+ const validActions = ['character', 'backspace', 'delete', 'enter', 'tab', 'space', 'shift', 'caps-lock', 'escape', 'arrow-left', 'arrow-right', 'arrow-up', 'arrow-down', 'home', 'end'];
39
+ return [{
40
+ code: String(item.code), label: String(item.label),
41
+ ...(item.value != null ? { value: String(item.value) } : {}),
42
+ ...(item.shiftLabel != null ? { shiftLabel: String(item.shiftLabel) } : {}),
43
+ ...(item.shiftValue != null ? { shiftValue: String(item.shiftValue) } : {}),
44
+ action: validActions.includes(actionName) ? actionName : 'character',
45
+ width: Number.isFinite(Number(item.width)) ? Math.max(.5, Math.min(12, Number(item.width))) : 1,
46
+ ...(typeof item.disabled === 'boolean' ? { disabled: item.disabled } : {}),
47
+ }];
48
+ });
49
+ return keys.length ? [keys] : [];
50
+ });
51
+ return rows.length ? rows : HY_VIRTUAL_KEYBOARD_QWERTY_LAYOUT;
52
+ }
53
+ function delay(milliseconds) { return new Promise(resolve => window.setTimeout(resolve, Math.max(0, milliseconds))); }
54
+ /** Themeable on-screen keyboard with programmable key actions and optional input binding. */
55
+ export class HYVirtualKeyboard extends HTMLElement {
56
+ _keyboard = document.createElement('div');
57
+ _layout = HY_VIRTUAL_KEYBOARD_QWERTY_LAYOUT;
58
+ _layoutAssigned = false;
59
+ _target = null;
60
+ _pressed = new Set();
61
+ _pressedValues = new Map();
62
+ _buttons = new Map();
63
+ _shift = false;
64
+ _capsLock = false;
65
+ _listeners = null;
66
+ _operation = 0;
67
+ static get observedAttributes() { return ['layout', 'mode', 'disabled', 'physical-sync', 'aria-label']; }
68
+ constructor() {
69
+ super();
70
+ const root = this.attachShadow({ mode: 'open' });
71
+ const style = document.createElement('style');
72
+ style.textContent = `
73
+ :host { display: block; min-width: 0; color: var(--hy-text-color, #eef3ff); font: 12px/1 system-ui, sans-serif; }
74
+ .keyboard { display: grid; gap: var(--hy-virtual-keyboard-row-gap, 7px); box-sizing: border-box; padding: var(--hy-virtual-keyboard-padding, 12px); border: 1px solid var(--hy-border-color, #3d4654); border-radius: var(--hy-virtual-keyboard-radius, 11px); background: var(--hy-virtual-keyboard-bg, var(--hy-surface-color, #111722)); box-shadow: var(--hy-virtual-keyboard-shadow, 0 16px 40px rgba(0, 0, 0, .22)); user-select: none; touch-action: manipulation; }
75
+ .row { display: flex; gap: var(--hy-virtual-keyboard-key-gap, 6px); min-width: 0; }
76
+ .key { position: relative; min-width: 0; height: var(--hy-virtual-keyboard-key-height, 42px); flex: var(--_key-width, 1) 1 0; overflow: hidden; padding: 0 5px; border: 1px solid var(--hy-virtual-keyboard-key-border, var(--hy-border-color, #3d4654)); border-radius: var(--hy-virtual-keyboard-key-radius, 7px); color: var(--hy-virtual-keyboard-key-color, var(--hy-text-color, #eef3ff)); background: var(--hy-virtual-keyboard-key-bg, var(--hy-surface-elevated-color, #171d27)); box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--hy-border-color, #3d4654) 65%, transparent); cursor: pointer; font: inherit; text-overflow: ellipsis; white-space: nowrap; transition: translate 70ms ease, background-color 70ms ease, box-shadow 70ms ease; }
77
+ .key:hover:not(:disabled) { border-color: var(--hy-hover-border-color, #596779); background: var(--hy-hover-bg-color, #273241); }
78
+ .key:focus-visible { z-index: 1; outline: 2px solid var(--hy-focus-border-color, #79a8ff); outline-offset: 1px; }
79
+ .key.pressed, .key[aria-pressed="true"] { translate: 0 1px; border-color: var(--hy-selected-bg-color, #554cc6); background: var(--hy-virtual-keyboard-active-bg, color-mix(in srgb, var(--hy-selected-bg-color, #554cc6) 50%, var(--hy-surface-elevated-color, #171d27))); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hy-focus-border-color, #79a8ff) 24%, transparent); }
80
+ .key:disabled { opacity: .42; cursor: not-allowed; }
81
+ :host([disabled]) .keyboard { opacity: .62; }
82
+ @media (max-width: 620px) { .keyboard { --hy-virtual-keyboard-padding: 7px; --hy-virtual-keyboard-row-gap: 5px; --hy-virtual-keyboard-key-gap: 3px; --hy-virtual-keyboard-key-height: 36px; font-size: 9px; } }
83
+ @media (prefers-reduced-motion: reduce) { .key { transition: none; } }
84
+ `;
85
+ this._keyboard.className = 'keyboard';
86
+ this._keyboard.part.add('keyboard');
87
+ this._keyboard.setAttribute('role', 'group');
88
+ root.append(style, this._keyboard);
89
+ }
90
+ connectedCallback() {
91
+ if (!this._layoutAssigned && this.hasAttribute('layout'))
92
+ this._readLayoutAttribute();
93
+ else if (!this._layoutAssigned)
94
+ this._applyModeLayout();
95
+ this._render();
96
+ this._connectPhysicalKeyboard();
97
+ }
98
+ disconnectedCallback() { this._operation += 1; this._disconnectPhysicalKeyboard(); this._pressed.clear(); this._pressedValues.clear(); }
99
+ attributeChangedCallback(name, oldValue, newValue) {
100
+ if (oldValue === newValue)
101
+ return;
102
+ if (name === 'layout' && !this._layoutAssigned) {
103
+ this._readLayoutAttribute();
104
+ this._render();
105
+ }
106
+ else if (name === 'mode') {
107
+ this._layoutAssigned = false;
108
+ this._applyModeLayout();
109
+ this._render();
110
+ }
111
+ else if (name === 'physical-sync')
112
+ this._connectPhysicalKeyboard();
113
+ else if (name === 'aria-label')
114
+ this._syncLabel();
115
+ else if (name === 'disabled')
116
+ this._syncVisuals();
117
+ }
118
+ get layout() { return this._layout; }
119
+ set layout(value) { this._layoutAssigned = true; this._layout = normalizeLayout(value); this._render(); }
120
+ get mode() { return this.getAttribute('mode') === 'numeric' ? 'numeric' : 'qwerty'; }
121
+ set mode(value) {
122
+ const normalized = value === 'numeric' ? 'numeric' : 'qwerty';
123
+ this._layoutAssigned = false;
124
+ if (this.getAttribute('mode') === normalized) {
125
+ this._applyModeLayout();
126
+ this._render();
127
+ }
128
+ else
129
+ this.setAttribute('mode', normalized);
130
+ }
131
+ get target() { return this._target; }
132
+ set target(value) { this._target = value instanceof HTMLInputElement || value instanceof HTMLTextAreaElement ? value : null; }
133
+ get disabled() { return this.hasAttribute('disabled'); }
134
+ set disabled(value) { this.toggleAttribute('disabled', Boolean(value)); }
135
+ get physicalSync() { return this.hasAttribute('physical-sync'); }
136
+ set physicalSync(value) { this.toggleAttribute('physical-sync', Boolean(value)); }
137
+ get shiftActive() { return this._shift; }
138
+ get capsLock() { return this._capsLock; }
139
+ get pressedKeys() { return [...this._pressed]; }
140
+ press(code, source = 'api') { this._press(String(code), source, source !== 'physical'); }
141
+ release(code, source = 'api') { this._release(String(code), source); }
142
+ async tap(code, options = {}) {
143
+ const operation = this._operation;
144
+ const requested = options.holdDuration == null ? 70 : Number(options.holdDuration);
145
+ const holdDuration = Number.isFinite(requested) ? Math.min(10_000, Math.max(0, requested)) : 70;
146
+ this.press(code, 'api');
147
+ await delay(holdDuration);
148
+ if (operation === this._operation && this.isConnected)
149
+ this.release(code, 'api');
150
+ }
151
+ async type(text, options = {}) {
152
+ const operation = ++this._operation;
153
+ const requested = options.interval == null ? 45 : Number(options.interval);
154
+ const interval = Number.isFinite(requested) ? Math.min(10_000, Math.max(0, requested)) : 45;
155
+ for (const character of String(text)) {
156
+ if (operation !== this._operation || !this.isConnected)
157
+ return;
158
+ const match = this._findCharacterKey(character);
159
+ if (match) {
160
+ const isLetter = match.key.code.startsWith('Key');
161
+ this._shift = isLetter ? match.shifted !== this._capsLock : match.shifted;
162
+ this._press(match.key.code, 'api', true);
163
+ this._release(match.key.code, 'api');
164
+ }
165
+ else
166
+ this._insertText(character, 'insertText', { code: 'Unidentified', key: character, action: 'character', source: 'api' });
167
+ if (interval)
168
+ await delay(interval);
169
+ }
170
+ }
171
+ clear() {
172
+ if (!this._target || this.disabled || this._target.disabled || this._target.readOnly)
173
+ return;
174
+ this._target.value = '';
175
+ this._target.setSelectionRange(0, 0);
176
+ this._dispatchTargetInput('deleteContent', null);
177
+ this._emitInput({ code: 'Clear', key: '', action: 'delete', source: 'api' }, 'deleteContent');
178
+ }
179
+ _readLayoutAttribute() { try {
180
+ this._layout = normalizeLayout(JSON.parse(this.getAttribute('layout') ?? '[]'));
181
+ }
182
+ catch {
183
+ this._layout = HY_VIRTUAL_KEYBOARD_QWERTY_LAYOUT;
184
+ } }
185
+ _applyModeLayout() { this._layout = this.mode === 'numeric' ? HY_VIRTUAL_KEYBOARD_NUMERIC_LAYOUT : HY_VIRTUAL_KEYBOARD_QWERTY_LAYOUT; }
186
+ _render() {
187
+ if (!this.isConnected)
188
+ return;
189
+ this._buttons.clear();
190
+ const rows = this._layout.map((keys, rowIndex) => {
191
+ const row = document.createElement('div');
192
+ row.className = 'row';
193
+ row.part.add('row');
194
+ row.setAttribute('role', 'group');
195
+ row.setAttribute('aria-label', `Keyboard row ${rowIndex + 1}`);
196
+ for (const key of keys) {
197
+ const button = document.createElement('button');
198
+ button.type = 'button';
199
+ button.className = 'key';
200
+ button.part.add('key');
201
+ button.dataset.code = key.code;
202
+ button.style.setProperty('--_key-width', String(key.width ?? 1));
203
+ button.disabled = Boolean(key.disabled) || this.disabled;
204
+ const safePart = key.code.toLowerCase().replace(/[^a-z0-9_-]/gu, '-');
205
+ if (safePart)
206
+ button.part.add(`key-${safePart}`);
207
+ button.addEventListener('pointerdown', event => { if (button.disabled)
208
+ return; event.preventDefault(); button.setPointerCapture(event.pointerId); this._press(key.code, 'pointer', true); });
209
+ button.addEventListener('pointerup', event => { if (button.hasPointerCapture(event.pointerId))
210
+ button.releasePointerCapture(event.pointerId); this._release(key.code, 'pointer'); });
211
+ button.addEventListener('pointercancel', () => this._release(key.code, 'pointer'));
212
+ button.addEventListener('click', event => { if (event.detail === 0) {
213
+ this._press(key.code, 'keyboard', true);
214
+ this._release(key.code, 'keyboard');
215
+ } });
216
+ this._buttons.set(key.code, button);
217
+ row.append(button);
218
+ }
219
+ return row;
220
+ });
221
+ this._keyboard.replaceChildren(...rows);
222
+ this._syncLabel();
223
+ this._syncVisuals();
224
+ }
225
+ _syncLabel() { this._keyboard.setAttribute('aria-label', this.getAttribute('aria-label') || 'Virtual keyboard'); }
226
+ _syncVisuals() {
227
+ for (const key of this._layout.flat()) {
228
+ const button = this._buttons.get(key.code);
229
+ if (!button)
230
+ continue;
231
+ const resolved = this._resolveKey(key);
232
+ button.textContent = resolved.label;
233
+ button.title = key.label;
234
+ button.disabled = Boolean(key.disabled) || this.disabled;
235
+ button.classList.toggle('pressed', this._pressed.has(key.code));
236
+ const toggled = key.action === 'shift' ? this._shift : key.action === 'caps-lock' ? this._capsLock : false;
237
+ if (key.action === 'shift' || key.action === 'caps-lock')
238
+ button.setAttribute('aria-pressed', String(toggled));
239
+ else
240
+ button.removeAttribute('aria-pressed');
241
+ button.setAttribute('aria-label', key.label.length === 1 ? resolved.key : key.label);
242
+ }
243
+ }
244
+ _press(code, source, apply) {
245
+ if (this.disabled || this._pressed.has(code))
246
+ return;
247
+ const key = this._findKey(code);
248
+ if (!key || key.disabled)
249
+ return;
250
+ this._pressed.add(code);
251
+ const resolved = this._resolveKey(key);
252
+ this._pressedValues.set(code, resolved.key);
253
+ if (key.action === 'shift') {
254
+ if (source !== 'physical')
255
+ this._shift = !this._shift;
256
+ }
257
+ else if (key.action === 'caps-lock') {
258
+ if (source !== 'physical')
259
+ this._capsLock = !this._capsLock;
260
+ }
261
+ else if (apply)
262
+ this._applyKey(key, resolved.key, source);
263
+ this._syncVisuals();
264
+ this._dispatchKeyEvent('virtual-keyboard-down', key, resolved.key, source);
265
+ if (apply && key.action !== 'shift' && key.action !== 'caps-lock' && this._shift) {
266
+ this._shift = false;
267
+ this._syncVisuals();
268
+ }
269
+ }
270
+ _release(code, source) {
271
+ if (!this._pressed.delete(code))
272
+ return;
273
+ const key = this._findKey(code);
274
+ if (!key)
275
+ return;
276
+ const resolvedValue = this._pressedValues.get(code) ?? this._resolveKey(key).key;
277
+ this._pressedValues.delete(code);
278
+ this._syncVisuals();
279
+ this._dispatchKeyEvent('virtual-keyboard-up', key, resolvedValue, source);
280
+ }
281
+ _applyKey(key, resolvedValue, source) {
282
+ const descriptor = { code: key.code, key: resolvedValue, action: key.action ?? 'character', source };
283
+ if (key.action === 'shift' || key.action === 'caps-lock')
284
+ return;
285
+ if (key.action === 'backspace')
286
+ this._deleteBackward(descriptor);
287
+ else if (key.action === 'delete')
288
+ this._deleteForward(descriptor);
289
+ else if (key.action === 'arrow-left')
290
+ this._moveCaret(-1);
291
+ else if (key.action === 'arrow-right')
292
+ this._moveCaret(1);
293
+ else if (key.action === 'arrow-up')
294
+ this._moveCaretVertical(-1);
295
+ else if (key.action === 'arrow-down')
296
+ this._moveCaretVertical(1);
297
+ else if (key.action === 'home')
298
+ this._setCaret(0);
299
+ else if (key.action === 'end')
300
+ this._setCaret(this._target?.value.length ?? 0);
301
+ else if (key.action === 'escape')
302
+ this._target?.blur();
303
+ else if (key.action === 'enter') {
304
+ if (this._target instanceof HTMLTextAreaElement)
305
+ this._insertText('\n', 'insertLineBreak', descriptor);
306
+ else
307
+ this._target?.dispatchEvent(new Event('change', { bubbles: true }));
308
+ }
309
+ else if (key.action === 'tab') {
310
+ if (this._target instanceof HTMLTextAreaElement)
311
+ this._insertText('\t', 'insertText', descriptor);
312
+ }
313
+ else
314
+ this._insertText(key.action === 'space' ? ' ' : resolvedValue, 'insertText', descriptor);
315
+ }
316
+ _insertText(text, inputType, descriptor) {
317
+ const target = this._target;
318
+ if (!target || target.disabled || target.readOnly)
319
+ return;
320
+ const start = target.selectionStart ?? target.value.length;
321
+ const end = target.selectionEnd ?? start;
322
+ target.setRangeText(text, start, end, 'end');
323
+ this._dispatchTargetInput(inputType, text);
324
+ this._emitInput(descriptor, inputType);
325
+ }
326
+ _deleteBackward(descriptor) {
327
+ const target = this._target;
328
+ if (!target || target.disabled || target.readOnly)
329
+ return;
330
+ const start = target.selectionStart ?? 0;
331
+ const end = target.selectionEnd ?? start;
332
+ if (start === end && start === 0)
333
+ return;
334
+ target.setRangeText('', start === end ? start - 1 : start, end, 'end');
335
+ this._dispatchTargetInput('deleteContentBackward', null);
336
+ this._emitInput(descriptor, 'deleteContentBackward');
337
+ }
338
+ _deleteForward(descriptor) {
339
+ const target = this._target;
340
+ if (!target || target.disabled || target.readOnly)
341
+ return;
342
+ const start = target.selectionStart ?? 0;
343
+ const end = target.selectionEnd ?? start;
344
+ if (start === end && end >= target.value.length)
345
+ return;
346
+ target.setRangeText('', start, start === end ? end + 1 : end, 'end');
347
+ this._dispatchTargetInput('deleteContentForward', null);
348
+ this._emitInput(descriptor, 'deleteContentForward');
349
+ }
350
+ _moveCaret(delta) { const target = this._target; if (!target)
351
+ return; const position = Math.max(0, Math.min(target.value.length, (target.selectionStart ?? 0) + delta)); this._setCaret(position); }
352
+ _moveCaretVertical(direction) {
353
+ const target = this._target;
354
+ if (!(target instanceof HTMLTextAreaElement))
355
+ return;
356
+ const value = target.value;
357
+ const position = target.selectionStart ?? 0;
358
+ const lineStart = value.lastIndexOf('\n', position - 1) + 1;
359
+ const column = position - lineStart;
360
+ if (direction < 0) {
361
+ if (lineStart === 0)
362
+ return;
363
+ const previousEnd = lineStart - 1;
364
+ const previousStart = value.lastIndexOf('\n', previousEnd - 1) + 1;
365
+ this._setCaret(previousStart + Math.min(column, previousEnd - previousStart));
366
+ }
367
+ else {
368
+ const currentEnd = value.indexOf('\n', position);
369
+ if (currentEnd < 0)
370
+ return;
371
+ const nextStart = currentEnd + 1;
372
+ const nextEnd = value.indexOf('\n', nextStart);
373
+ const length = (nextEnd < 0 ? value.length : nextEnd) - nextStart;
374
+ this._setCaret(nextStart + Math.min(column, length));
375
+ }
376
+ }
377
+ _setCaret(position) { const target = this._target; if (!target)
378
+ return; target.focus({ preventScroll: true }); target.setSelectionRange(position, position); }
379
+ _dispatchTargetInput(inputType, data) {
380
+ if (!this._target)
381
+ return;
382
+ try {
383
+ this._target.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true, inputType, data }));
384
+ }
385
+ catch {
386
+ this._target.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
387
+ }
388
+ }
389
+ _emitInput(descriptor, inputType) {
390
+ if (!this._target)
391
+ return;
392
+ this.dispatchEvent(new CustomEvent('virtual-keyboard-input', { bubbles: true, composed: true, detail: { ...descriptor, value: this._target.value, inputType, pressedKeys: [...this._pressed], shiftKey: this._shift, capsLock: this._capsLock } }));
393
+ }
394
+ _dispatchKeyEvent(name, key, resolvedValue, source) {
395
+ this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail: { code: key.code, key: resolvedValue, action: key.action ?? 'character', source, pressedKeys: [...this._pressed], shiftKey: this._shift, capsLock: this._capsLock } }));
396
+ }
397
+ _resolveKey(key) {
398
+ const isLetter = key.code.startsWith('Key') && LETTERS.includes(key.code.slice(3).toUpperCase());
399
+ const shifted = isLetter ? this._shift !== this._capsLock : this._shift;
400
+ const value = shifted ? (key.shiftValue ?? key.shiftLabel ?? key.value ?? key.label) : (key.value ?? key.label);
401
+ const label = key.action && key.action !== 'character' ? key.label : shifted ? (key.shiftLabel ?? (isLetter ? key.label.toUpperCase() : key.label)) : key.label;
402
+ return { key: value, label };
403
+ }
404
+ _findKey(code) { return this._layout.flat().find(key => key.code === code) ?? null; }
405
+ _findCharacterKey(character) {
406
+ for (const key of this._layout.flat()) {
407
+ const normal = key.value ?? (key.action === 'space' ? ' ' : key.label);
408
+ const shifted = key.shiftValue ?? key.shiftLabel;
409
+ if (normal === character)
410
+ return { key, shifted: false };
411
+ if (shifted === character)
412
+ return { key, shifted: true };
413
+ }
414
+ return null;
415
+ }
416
+ _connectPhysicalKeyboard() {
417
+ this._disconnectPhysicalKeyboard();
418
+ if (!this.isConnected || !this.physicalSync)
419
+ return;
420
+ const abort = new AbortController();
421
+ this._listeners = abort;
422
+ document.addEventListener('keydown', event => { if (event.repeat)
423
+ return; this._shift = event.shiftKey; this._capsLock = event.getModifierState('CapsLock'); this._press(event.code, 'physical', false); }, { signal: abort.signal });
424
+ document.addEventListener('keyup', event => { this._shift = event.shiftKey; this._capsLock = event.getModifierState('CapsLock'); this._release(event.code, 'physical'); this._syncVisuals(); }, { signal: abort.signal });
425
+ }
426
+ _disconnectPhysicalKeyboard() { this._listeners?.abort(); this._listeners = null; }
427
+ }
428
+ export function defineVirtualKeyboardComponents() {
429
+ if (!customElements.get('hy-virtual-keyboard'))
430
+ customElements.define('hy-virtual-keyboard', HYVirtualKeyboard);
431
+ }
432
+ //# sourceMappingURL=virtual-keyboard.js.map
@@ -0,0 +1,82 @@
1
+ export type HYVirtualMouseButton = 'left' | 'middle' | 'right';
2
+ export type HYVirtualMouseState = 'idle' | 'left-down' | 'middle-down' | 'right-down';
3
+ export interface HYVirtualMousePosition {
4
+ readonly x: number;
5
+ readonly y: number;
6
+ }
7
+ export interface HYVirtualMouseMoveOptions {
8
+ readonly animate?: boolean;
9
+ readonly duration?: number;
10
+ readonly easing?: string;
11
+ }
12
+ export interface HYVirtualMouseClickOptions {
13
+ readonly holdDuration?: number;
14
+ }
15
+ export type HYVirtualMouseImages = Partial<Record<HYVirtualMouseState, string>>;
16
+ export interface HYVirtualMouseMoveDetail {
17
+ readonly from: HYVirtualMousePosition;
18
+ readonly to: HYVirtualMousePosition;
19
+ readonly animated: boolean;
20
+ readonly duration: number;
21
+ }
22
+ export interface HYVirtualMouseButtonDetail extends HYVirtualMousePosition {
23
+ readonly button: HYVirtualMouseButton;
24
+ readonly buttons: readonly HYVirtualMouseButton[];
25
+ readonly state: HYVirtualMouseState;
26
+ }
27
+ /** Fixed, non-interactive cursor visual for tutorials, playback, and remote-operation feedback. */
28
+ export declare class HYVirtualMouse extends HTMLElement {
29
+ private readonly _cursor;
30
+ private readonly _shape;
31
+ private readonly _image;
32
+ private readonly _indicator;
33
+ private readonly _pressed;
34
+ private _images;
35
+ private _x;
36
+ private _y;
37
+ private _reflectingPosition;
38
+ private _moveTimer;
39
+ private _resolveMove;
40
+ private readonly _delays;
41
+ static get observedAttributes(): string[];
42
+ constructor();
43
+ connectedCallback(): void;
44
+ disconnectedCallback(): void;
45
+ attributeChangedCallback(name: string): void;
46
+ get x(): number;
47
+ set x(value: number);
48
+ get y(): number;
49
+ set y(value: number);
50
+ get animated(): boolean;
51
+ set animated(value: boolean);
52
+ get duration(): number;
53
+ set duration(value: number);
54
+ get easing(): string;
55
+ set easing(value: string);
56
+ get state(): HYVirtualMouseState;
57
+ get pressedButtons(): readonly HYVirtualMouseButton[];
58
+ get images(): HYVirtualMouseImages;
59
+ set images(value: HYVirtualMouseImages);
60
+ setStateImage(state: HYVirtualMouseState, source: string | null): void;
61
+ show(): void;
62
+ hide(): void;
63
+ moveBy(deltaX: number, deltaY: number, options?: HYVirtualMouseMoveOptions): Promise<void>;
64
+ moveTo(x: number, y: number, options?: HYVirtualMouseMoveOptions): Promise<void>;
65
+ press(button?: HYVirtualMouseButton): void;
66
+ release(button?: HYVirtualMouseButton): void;
67
+ releaseAll(): void;
68
+ click(button?: HYVirtualMouseButton, options?: HYVirtualMouseClickOptions): Promise<void>;
69
+ leftClick(options?: HYVirtualMouseClickOptions): Promise<void>;
70
+ middleClick(options?: HYVirtualMouseClickOptions): Promise<void>;
71
+ rightClick(options?: HYVirtualMouseClickOptions): Promise<void>;
72
+ private _setPosition;
73
+ private _applyPosition;
74
+ private _currentState;
75
+ private _syncState;
76
+ private _syncImage;
77
+ private _dispatchButtonEvent;
78
+ private _pulse;
79
+ private _cancelMove;
80
+ private _wait;
81
+ }
82
+ export declare function defineVirtualMouseComponents(): void;