@haiyue/ui 0.1.1 → 0.2.1

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 (99) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +504 -1
  3. package/dist/adapters/web/controls.d.ts +19 -0
  4. package/dist/adapters/web/controls.js +284 -0
  5. package/dist/adapters/web/dom.d.ts +10 -0
  6. package/dist/adapters/web/dom.js +98 -0
  7. package/dist/adapters/web/index.d.ts +3 -0
  8. package/dist/adapters/web/index.js +3 -0
  9. package/dist/adapters/web/renderer.d.ts +18 -0
  10. package/dist/adapters/web/renderer.js +545 -0
  11. package/dist/adapters/web/styles.d.ts +3 -0
  12. package/dist/adapters/web/styles.js +62 -0
  13. package/dist/adapters/web/types.d.ts +46 -0
  14. package/dist/adapters/web/types.js +2 -0
  15. package/dist/affix.d.ts +35 -0
  16. package/dist/affix.js +137 -0
  17. package/dist/badge.d.ts +45 -0
  18. package/dist/badge.js +168 -0
  19. package/dist/border-beam.js +3 -1
  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/cascader.d.ts +65 -0
  27. package/dist/cascader.js +479 -0
  28. package/dist/collapse.d.ts +55 -0
  29. package/dist/collapse.js +261 -0
  30. package/dist/core/actions.d.ts +32 -0
  31. package/dist/core/actions.js +145 -0
  32. package/dist/core/data.d.ts +4 -0
  33. package/dist/core/data.js +27 -0
  34. package/dist/core/index.d.ts +7 -0
  35. package/dist/core/index.js +6 -0
  36. package/dist/core/platform-view.d.ts +17 -0
  37. package/dist/core/platform-view.js +84 -0
  38. package/dist/core/selection.d.ts +14 -0
  39. package/dist/core/selection.js +93 -0
  40. package/dist/core/text.d.ts +31 -0
  41. package/dist/core/text.js +154 -0
  42. package/dist/core/types.d.ts +250 -0
  43. package/dist/core/types.js +2 -0
  44. package/dist/core/validation.d.ts +24 -0
  45. package/dist/core/validation.js +238 -0
  46. package/dist/date-picker.d.ts +60 -0
  47. package/dist/date-picker.js +355 -0
  48. package/dist/dialog.d.ts +4 -0
  49. package/dist/dialog.js +23 -5
  50. package/dist/divider.d.ts +33 -0
  51. package/dist/divider.js +113 -0
  52. package/dist/draggable.d.ts +78 -0
  53. package/dist/draggable.js +255 -0
  54. package/dist/expandable.d.ts +32 -0
  55. package/dist/expandable.js +136 -0
  56. package/dist/index.d.ts +47 -1
  57. package/dist/index.js +69 -0
  58. package/dist/input.d.ts +30 -1
  59. package/dist/input.js +166 -54
  60. package/dist/portable/index.d.ts +4 -0
  61. package/dist/portable/index.js +3 -0
  62. package/dist/portable/page.d.ts +38 -0
  63. package/dist/portable/page.js +361 -0
  64. package/dist/portable/theme.d.ts +19 -0
  65. package/dist/portable/theme.js +40 -0
  66. package/dist/portable/tree.d.ts +10 -0
  67. package/dist/portable/tree.js +96 -0
  68. package/dist/range.d.ts +3 -0
  69. package/dist/range.js +16 -5
  70. package/dist/rate.d.ts +46 -0
  71. package/dist/rate.js +193 -0
  72. package/dist/resizable.d.ts +97 -0
  73. package/dist/resizable.js +426 -0
  74. package/dist/skeleton.d.ts +41 -0
  75. package/dist/skeleton.js +141 -0
  76. package/dist/steps.d.ts +45 -0
  77. package/dist/steps.js +203 -0
  78. package/dist/table.d.ts +106 -0
  79. package/dist/table.js +689 -0
  80. package/dist/tabs.d.ts +7 -0
  81. package/dist/tabs.js +90 -16
  82. package/dist/tag.d.ts +55 -0
  83. package/dist/tag.js +225 -0
  84. package/dist/time-picker.d.ts +62 -0
  85. package/dist/time-picker.js +298 -0
  86. package/dist/timeline.d.ts +48 -0
  87. package/dist/timeline.js +201 -0
  88. package/dist/toast.d.ts +55 -0
  89. package/dist/toast.js +224 -0
  90. package/dist/tour.d.ts +89 -0
  91. package/dist/tour.js +306 -0
  92. package/dist/virtual-keyboard.d.ts +95 -0
  93. package/dist/virtual-keyboard.js +432 -0
  94. package/dist/virtual-list.js +12 -10
  95. package/dist/virtual-mouse.d.ts +82 -0
  96. package/dist/virtual-mouse.js +358 -0
  97. package/dist/watermark.d.ts +44 -0
  98. package/dist/watermark.js +148 -0
  99. package/package.json +135 -7
package/dist/input.js CHANGED
@@ -1,9 +1,18 @@
1
1
  export class HYInput extends HTMLElement {
2
+ _control = document.createElement('div');
3
+ _prefix = document.createElement('span');
4
+ _prefixSlot = document.createElement('slot');
5
+ _prefixText = document.createElement('span');
2
6
  _input = document.createElement('input');
7
+ _suffix = document.createElement('span');
8
+ _suffixSlot = document.createElement('slot');
9
+ _suffixText = document.createElement('span');
10
+ _searchButton = document.createElement('button');
3
11
  static get observedAttributes() {
4
12
  return [
5
13
  'type', 'value', 'min', 'max', 'step', 'placeholder', 'disabled', 'readonly',
6
- 'required', 'name', 'autocomplete', 'aria-label', 'title',
14
+ 'required', 'name', 'autocomplete', 'aria-label', 'title', 'prefix', 'suffix',
15
+ 'search-label',
7
16
  ];
8
17
  }
9
18
  constructor() {
@@ -11,74 +20,132 @@ export class HYInput extends HTMLElement {
11
20
  const root = this.attachShadow({ mode: 'open' });
12
21
  const style = document.createElement('style');
13
22
  style.textContent = `
14
- :host {
15
- display: block;
23
+ :host { display: block; width: 100%; min-width: 0; }
24
+ .control {
25
+ display: flex;
16
26
  width: 100%;
17
27
  min-width: 0;
18
- }
19
- input {
20
- box-sizing: border-box;
21
- width: 100%;
22
28
  height: var(--hy-input-height, 28px);
23
- min-width: 0;
24
- padding: 0 8px;
29
+ box-sizing: border-box;
30
+ align-items: stretch;
31
+ overflow: hidden;
25
32
  border: 1px solid var(--hy-border-color, #303746);
26
- border-radius: 4px;
27
- outline: none;
33
+ border-radius: var(--hy-input-radius, 4px);
28
34
  color: var(--hy-text-color, #d8e2f2);
29
35
  background: var(--hy-input-bg-color, var(--hy-surface-color, #121822));
36
+ transition: border-color 120ms ease, box-shadow 120ms ease;
37
+ }
38
+ .control:hover:not(.disabled):not(.readonly) { border-color: var(--hy-hover-border-color, #435268); }
39
+ .control:focus-within { border-color: var(--hy-focus-border-color, #3d6fa8); box-shadow: 0 0 0 2px var(--hy-focus-ring-color, rgba(61, 111, 168, 0.24)); }
40
+ .control.invalid { border-color: var(--hy-invalid-border-color, #b64f62); }
41
+ .control.disabled, .control.readonly { opacity: 0.55; }
42
+ input {
43
+ flex: 1 1 auto;
44
+ width: 0;
45
+ min-width: 0;
46
+ box-sizing: border-box;
47
+ padding: 0 var(--hy-input-padding-inline, 8px);
48
+ border: 0;
49
+ outline: none;
50
+ color: inherit;
51
+ background: transparent;
30
52
  font: var(--hy-input-font, 12px system-ui, sans-serif);
31
53
  color-scheme: inherit;
32
54
  }
33
- input:hover:not(:disabled):not(:read-only) {
34
- border-color: var(--hy-hover-border-color, #435268);
35
- }
36
- input:focus {
37
- border-color: var(--hy-focus-border-color, #3d6fa8);
38
- box-shadow: 0 0 0 2px var(--hy-focus-ring-color, rgba(61, 111, 168, 0.24));
39
- }
40
- input:invalid {
41
- border-color: var(--hy-invalid-border-color, #b64f62);
42
- }
43
- input:disabled,
44
- input:read-only {
45
- opacity: 0.55;
46
- cursor: default;
47
- }
48
- input[type="color"] {
49
- padding: 3px;
50
- cursor: pointer;
51
- }
55
+ input:disabled, input:read-only { cursor: default; }
56
+ input[type="color"] { min-width: 42px; padding: 3px; cursor: pointer; }
52
57
  input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
53
- input[type="color"]::-webkit-color-swatch {
58
+ input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 2px; }
59
+ input[type="search"]::-webkit-search-cancel-button { cursor: pointer; }
60
+ .affix {
61
+ display: inline-flex;
62
+ flex: 0 0 auto;
63
+ align-items: center;
64
+ max-width: 42%;
65
+ padding-inline: var(--hy-input-affix-padding, 8px);
66
+ overflow: hidden;
67
+ color: var(--hy-input-affix-color, var(--hy-muted-text-color, #8290a3));
68
+ font: var(--hy-input-affix-font, 12px system-ui, sans-serif);
69
+ text-overflow: ellipsis;
70
+ white-space: nowrap;
71
+ }
72
+ .prefix { padding-right: 0; }
73
+ .suffix { padding-left: 0; }
74
+ .affix[hidden], .search-button[hidden] { display: none; }
75
+ .search-button {
76
+ display: grid;
77
+ flex: 0 0 var(--hy-input-search-button-width, 32px);
78
+ width: var(--hy-input-search-button-width, 32px);
79
+ padding: 0;
80
+ place-items: center;
54
81
  border: 0;
55
- border-radius: 2px;
82
+ border-left: 1px solid var(--hy-border-color, #303746);
83
+ color: var(--hy-input-search-color, var(--hy-accent-color, #79a8ff));
84
+ background: var(--hy-input-search-bg, color-mix(in srgb, var(--hy-accent-color, #79a8ff) 11%, transparent));
85
+ cursor: pointer;
86
+ font: 16px/1 system-ui, sans-serif;
56
87
  }
88
+ .search-button:hover:not(:disabled) { background: var(--hy-input-search-hover-bg, color-mix(in srgb, var(--hy-accent-color, #79a8ff) 20%, transparent)); }
89
+ .search-button:focus-visible { outline: 2px solid var(--hy-focus-border-color, #3d6fa8); outline-offset: -3px; }
90
+ .search-button:disabled { cursor: default; }
91
+ @media (prefers-reduced-motion: reduce) { .control { transition: none; } }
57
92
  `;
58
- root.append(style, this._input);
93
+ this._control.className = 'control';
94
+ this._control.part.add('control');
95
+ this._prefix.className = 'affix prefix';
96
+ this._prefix.part.add('prefix');
97
+ this._prefixSlot.name = 'prefix';
98
+ this._prefixText.className = 'affix-text';
99
+ this._prefix.append(this._prefixSlot, this._prefixText);
100
+ this._input.part.add('input');
101
+ this._suffix.className = 'affix suffix';
102
+ this._suffix.part.add('suffix');
103
+ this._suffixSlot.name = 'suffix';
104
+ this._suffixText.className = 'affix-text';
105
+ this._suffix.append(this._suffixSlot, this._suffixText);
106
+ this._searchButton.className = 'search-button';
107
+ this._searchButton.type = 'button';
108
+ this._searchButton.part.add('search-button');
109
+ this._searchButton.textContent = '⌕';
110
+ this._control.append(this._prefix, this._input, this._suffix, this._searchButton);
111
+ root.append(style, this._control);
59
112
  }
60
113
  connectedCallback() {
61
114
  this._input.addEventListener('input', this._onInput);
62
115
  this._input.addEventListener('change', this._onChange);
116
+ this._input.addEventListener('keydown', this._onKeydown);
117
+ this._searchButton.addEventListener('click', this._onSearchClick);
118
+ this._prefixSlot.addEventListener('slotchange', this._onSlotChange);
119
+ this._suffixSlot.addEventListener('slotchange', this._onSlotChange);
63
120
  this._sync();
64
121
  }
65
122
  disconnectedCallback() {
66
123
  this._input.removeEventListener('input', this._onInput);
67
124
  this._input.removeEventListener('change', this._onChange);
125
+ this._input.removeEventListener('keydown', this._onKeydown);
126
+ this._searchButton.removeEventListener('click', this._onSearchClick);
127
+ this._prefixSlot.removeEventListener('slotchange', this._onSlotChange);
128
+ this._suffixSlot.removeEventListener('slotchange', this._onSlotChange);
68
129
  }
69
- attributeChangedCallback() {
70
- this._sync();
71
- }
130
+ attributeChangedCallback() { this._sync(); }
72
131
  get type() { return normalizedType(this.getAttribute('type')); }
73
132
  set type(value) { this.setAttribute('type', normalizedType(value)); }
74
133
  get value() { return this.getAttribute('value') ?? ''; }
75
134
  set value(value) {
76
- this.setAttribute('value', String(value));
77
- this._input.value = String(value);
78
- }
79
- get valueAsNumber() {
80
- return this._input.valueAsNumber;
135
+ const text = String(value);
136
+ if (this.getAttribute('value') !== text)
137
+ this.setAttribute('value', text);
138
+ if (this._input.value !== text)
139
+ this._input.value = text;
140
+ this._syncValidity();
81
141
  }
142
+ get valueAsNumber() { return this._input.valueAsNumber; }
143
+ get prefix() { return this.getAttribute('prefix') ?? ''; }
144
+ set prefix(value) { this._setOptionalAttribute('prefix', value); }
145
+ get suffix() { return this.getAttribute('suffix') ?? ''; }
146
+ set suffix(value) { this._setOptionalAttribute('suffix', value); }
147
+ get searchLabel() { return this.getAttribute('search-label')?.trim() || 'Search'; }
148
+ set searchLabel(value) { this._setOptionalAttribute('search-label', value); }
82
149
  get disabled() { return this.hasAttribute('disabled'); }
83
150
  set disabled(value) { this.toggleAttribute('disabled', value); }
84
151
  get readOnly() { return this.hasAttribute('readonly'); }
@@ -86,46 +153,91 @@ export class HYInput extends HTMLElement {
86
153
  get valid() { return this._input.validity.valid; }
87
154
  focus(options) { this._input.focus(options); }
88
155
  select() { this._input.select(); }
156
+ search() { return this._emitSearch('api'); }
89
157
  _sync() {
90
158
  const type = this.type;
91
159
  if (this._input.type !== type)
92
160
  this._input.type = type;
93
161
  const value = this.value;
94
162
  if (this._input.value !== value)
95
- this._input.value = type === 'color' && !/^#[0-9a-f]{6}$/iu.test(value)
96
- ? '#000000'
97
- : value;
163
+ this._input.value = type === 'color' && !/^#[0-9a-f]{6}$/iu.test(value) ? '#000000' : value;
98
164
  for (const name of ['min', 'max', 'step', 'placeholder', 'name', 'autocomplete', 'aria-label', 'title']) {
99
165
  const attribute = this.getAttribute(name);
100
- if (attribute === null)
101
- this._input.removeAttribute(name);
102
- else
166
+ if (attribute === null) {
167
+ if (this._input.hasAttribute(name))
168
+ this._input.removeAttribute(name);
169
+ }
170
+ else if (this._input.getAttribute(name) !== attribute)
103
171
  this._input.setAttribute(name, attribute);
104
172
  }
105
- this._input.disabled = this.disabled;
106
- this._input.readOnly = this.readOnly;
107
- this._input.required = this.hasAttribute('required');
173
+ // Reassigning even an unchanged placeholder or state can split Chromium's undo group.
174
+ if (this._input.disabled !== this.disabled)
175
+ this._input.disabled = this.disabled;
176
+ if (this._input.readOnly !== this.readOnly)
177
+ this._input.readOnly = this.readOnly;
178
+ if (this._input.required !== this.hasAttribute('required'))
179
+ this._input.required = this.hasAttribute('required');
180
+ this._searchButton.hidden = type !== 'search';
181
+ this._searchButton.disabled = this.disabled;
182
+ this._searchButton.setAttribute('aria-label', this.searchLabel);
183
+ this._control.classList.toggle('disabled', this.disabled);
184
+ this._control.classList.toggle('readonly', this.readOnly);
185
+ this._syncAffixes();
186
+ this._syncValidity();
108
187
  }
188
+ _syncAffixes() {
189
+ this._syncAffix(this._prefix, this._prefixSlot, this._prefixText, this.prefix);
190
+ this._syncAffix(this._suffix, this._suffixSlot, this._suffixText, this.suffix);
191
+ }
192
+ _syncAffix(container, slot, fallback, text) {
193
+ const hasAssignedContent = slot.assignedNodes({ flatten: true }).some(node => node.nodeType === Node.ELEMENT_NODE || Boolean(node.textContent?.trim()));
194
+ fallback.textContent = hasAssignedContent ? '' : text;
195
+ fallback.hidden = hasAssignedContent || !text;
196
+ container.hidden = !hasAssignedContent && !text;
197
+ }
198
+ _syncValidity() { this._control.classList.toggle('invalid', !this.valid); }
199
+ _setOptionalAttribute(name, value) {
200
+ const normalized = String(value ?? '').trim();
201
+ if (normalized)
202
+ this.setAttribute(name, normalized);
203
+ else
204
+ this.removeAttribute(name);
205
+ }
206
+ _onSlotChange = () => { this._syncAffixes(); };
109
207
  _onInput = () => {
110
208
  this.setAttribute('value', this._input.value);
209
+ this._syncValidity();
111
210
  };
112
211
  _onChange = () => {
113
212
  this.setAttribute('value', this._input.value);
114
- const number = this.type === 'number' && Number.isFinite(this._input.valueAsNumber)
115
- ? this._input.valueAsNumber
116
- : null;
213
+ const number = this.type === 'number' && Number.isFinite(this._input.valueAsNumber) ? this._input.valueAsNumber : null;
117
214
  this.dispatchEvent(new CustomEvent('value-change', {
118
215
  detail: { value: this._input.value, valueAsNumber: number, valid: this.valid },
119
216
  bubbles: true,
120
217
  composed: true,
121
218
  }));
122
219
  };
220
+ _onKeydown = (event) => {
221
+ if (event.key === 'Enter' && this.type === 'search' && !event.isComposing)
222
+ this._emitSearch('enter');
223
+ };
224
+ _onSearchClick = () => { this._emitSearch('button'); };
225
+ _emitSearch(source) {
226
+ if (this.disabled || this.type !== 'search')
227
+ return false;
228
+ return this.dispatchEvent(new CustomEvent('search', {
229
+ detail: { value: this._input.value, valid: this.valid, source },
230
+ bubbles: true,
231
+ composed: true,
232
+ cancelable: true,
233
+ }));
234
+ }
123
235
  }
124
236
  export function defineInputComponents() {
125
237
  if (!customElements.get('hy-input'))
126
238
  customElements.define('hy-input', HYInput);
127
239
  }
128
240
  function normalizedType(value) {
129
- return value === 'number' || value === 'color' ? value : 'text';
241
+ return value === 'number' || value === 'color' || value === 'search' || value === 'password' ? value : 'text';
130
242
  }
131
243
  //# sourceMappingURL=input.js.map
@@ -0,0 +1,4 @@
1
+ export { HYPage } from './page.js';
2
+ export type { HYPageOptions, HYPageStatus, HYBinding } from './page.js';
3
+ export { HY_THEME_TOKENS, hyNativeTheme, hyThemeVariables } from './theme.js';
4
+ export type { HYThemeVariables } from './theme.js';
@@ -0,0 +1,3 @@
1
+ export { HYPage } from './page.js';
2
+ export { HY_THEME_TOKENS, hyNativeTheme, hyThemeVariables } from './theme.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,38 @@
1
+ import type { HYCapabilities, HYComponentKind, HYComponentProps, HYDispose, HYNode, HYReason, HYRenderer, HYStore, HYUIEvent } from '../core/types.js';
2
+ export type HYPageStatus = 'new' | 'mounted' | 'suspended' | 'faulted' | 'disposed';
3
+ export interface HYPageOptions {
4
+ readonly onEvent?: (event: HYUIEvent) => void;
5
+ /** Scoped key from hyKey(). If unavailable the root receives focus. */
6
+ readonly defaultFocus?: string;
7
+ }
8
+ export type HYBinding<S> = {
9
+ [K in HYComponentKind]: {
10
+ readonly key: string;
11
+ readonly kind: K;
12
+ readonly select: (state: Readonly<S>) => Readonly<HYComponentProps[K]>;
13
+ readonly equal?: (a: Readonly<HYComponentProps[K]>, b: Readonly<HYComponentProps[K]>) => boolean;
14
+ };
15
+ }[HYComponentKind];
16
+ /** A synchronous, bounded page reconciler. It owns bindings/rendering, never the application store. */
17
+ export declare class HYPage {
18
+ #private;
19
+ private readonly options;
20
+ constructor(renderer: HYRenderer, options?: HYPageOptions);
21
+ get status(): HYPageStatus;
22
+ get revision(): number;
23
+ get capabilities(): HYCapabilities;
24
+ mount(root: HYNode): void;
25
+ render(root: HYNode): void;
26
+ setProps<K extends HYComponentKind>(key: string, kind: K, props: HYComponentProps[K]): void;
27
+ transaction(run: () => void): void;
28
+ /** One store subscription: a store transaction produces one final page batch across all bindings. */
29
+ bind<S, A>(store: HYStore<S, A>, bindings: readonly HYBinding<S>[]): HYDispose;
30
+ focus(key: string): boolean;
31
+ /** The shell chooses this single system-back owner. A modal consumes even a non-dismissible back. */
32
+ back(reason?: HYReason): boolean;
33
+ suspend(): void;
34
+ resume(): void;
35
+ /** Replaces the failed backend, preserving latest requested data and existing store bindings. */
36
+ rebuild(renderer: HYRenderer): void;
37
+ dispose(): void;
38
+ }