@haiyue/ui 0.2.1 → 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.
@@ -0,0 +1,24 @@
1
+ /** Public vector geometry. 24 × 24, 1.8 unit round strokes, no font or platform assets. */
2
+ export const HY_ICONS = Object.freeze({
3
+ save: [[[4, 3], [17, 3], [21, 7], [21, 21], [3, 21], [3, 3], [4, 3]], [[7, 3], [7, 9], [17, 9], [17, 3]], [[7, 21], [7, 14], [17, 14], [17, 21]]],
4
+ export: [[[12, 15], [12, 3]], [[7, 8], [12, 3], [17, 8]], [[4, 14], [4, 21], [20, 21], [20, 14]]],
5
+ play: [[[7, 4], [20, 12], [7, 20], [7, 4]]], pause: [[[8, 4], [8, 20]], [[16, 4], [16, 20]]],
6
+ reset: [[[4, 9], [6, 5], [10, 3], [15, 3], [19, 6], [21, 10], [21, 14], [18, 19], [14, 21], [9, 20], [6, 17]], [[4, 3], [4, 9], [10, 9]]],
7
+ step: [[[5, 5], [15, 12], [5, 19], [5, 5]], [[19, 5], [19, 19]]],
8
+ settings: [[[19.438, 10.007], [21.848, 10.264], [21.848, 13.736], [19.438, 13.993], [18.668, 15.85], [20.192, 17.736], [17.736, 20.192], [15.85, 18.668], [13.993, 19.438], [13.736, 21.848], [10.264, 21.848], [10.007, 19.438], [8.15, 18.668], [6.264, 20.192], [3.808, 17.736], [5.332, 15.85], [4.562, 13.993], [2.152, 13.736], [2.152, 10.264], [4.562, 10.007], [5.332, 8.15], [3.808, 6.264], [6.264, 3.808], [8.15, 5.332], [10.007, 4.562], [10.264, 2.152], [13.736, 2.152], [13.993, 4.562], [15.85, 5.332], [17.736, 3.808], [20.192, 6.264], [18.668, 8.15], [19.438, 10.007]], [[15.0, 12.0], [14.942, 12.585], [14.772, 13.148], [14.494, 13.667], [14.121, 14.121], [13.667, 14.494], [13.148, 14.772], [12.585, 14.942], [12.0, 15.0], [11.415, 14.942], [10.852, 14.772], [10.333, 14.494], [9.879, 14.121], [9.506, 13.667], [9.228, 13.148], [9.058, 12.585], [9.0, 12.0], [9.058, 11.415], [9.228, 10.852], [9.506, 10.333], [9.879, 9.879], [10.333, 9.506], [10.852, 9.228], [11.415, 9.058], [12.0, 9.0], [12.585, 9.058], [13.148, 9.228], [13.667, 9.506], [14.121, 9.879], [14.494, 10.333], [14.772, 10.852], [14.942, 11.415], [15.0, 12.0]]],
9
+ back: [[[14, 4], [6, 12], [14, 20]], [[6, 12], [21, 12]]], chevronDown: [[[6, 9], [12, 15], [18, 9]]],
10
+ check: [[[4, 12], [9, 17], [20, 6]]], close: [[[6, 6], [18, 18]], [[18, 6], [6, 18]]], add: [[[12, 4], [12, 20]], [[4, 12], [20, 12]]],
11
+ });
12
+ for (const paths of Object.values(HY_ICONS)) {
13
+ for (const points of paths) {
14
+ for (const point of points)
15
+ Object.freeze(point);
16
+ Object.freeze(points);
17
+ }
18
+ Object.freeze(paths);
19
+ }
20
+ /** Safe SVG built only from the library's numeric paths. Color inherits from its host. */
21
+ export function hyIconSvg(name) {
22
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${HY_ICONS[name].map(points => `<polyline points="${points.map(p => p.join(',')).join(' ')}"/>`).join('')}</svg>`;
23
+ }
24
+ //# sourceMappingURL=icons.js.map
@@ -2,3 +2,6 @@ export { HYPage } from './page.js';
2
2
  export type { HYPageOptions, HYPageStatus, HYBinding } from './page.js';
3
3
  export { HY_THEME_TOKENS, hyNativeTheme, hyThemeVariables } from './theme.js';
4
4
  export type { HYThemeVariables } from './theme.js';
5
+ export { HY_ICONS, hyIconSvg } from './icons.js';
6
+ export { hyResponsiveGrid, hyResponsiveWorkspace } from './responsive.js';
7
+ export type { HYResponsiveGridOptions, HYResponsiveWorkspaceOptions } from './responsive.js';
@@ -1,3 +1,5 @@
1
1
  export { HYPage } from './page.js';
2
2
  export { HY_THEME_TOKENS, hyNativeTheme, hyThemeVariables } from './theme.js';
3
+ export { HY_ICONS, hyIconSvg } from './icons.js';
4
+ export { hyResponsiveGrid, hyResponsiveWorkspace } from './responsive.js';
3
5
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,20 @@
1
+ /** Platform-independent logical-pixel layout decisions. */
2
+ export interface HYResponsiveGridOptions {
3
+ readonly minCardWidth?: number;
4
+ readonly maxColumns?: number;
5
+ readonly gap?: number;
6
+ }
7
+ export declare function hyResponsiveGrid(width: number, options?: HYResponsiveGridOptions): Readonly<{
8
+ columns: number;
9
+ gap: number;
10
+ cardWidth: number;
11
+ }>;
12
+ export interface HYResponsiveWorkspaceOptions {
13
+ readonly minPaneWidth?: number;
14
+ readonly gap?: number;
15
+ }
16
+ export declare function hyResponsiveWorkspace(width: number, height: number, options?: HYResponsiveWorkspaceOptions): Readonly<{
17
+ split: boolean;
18
+ gap: number;
19
+ referenceHeight: number;
20
+ }>;
@@ -0,0 +1,15 @@
1
+ export function hyResponsiveGrid(width, options = {}) {
2
+ const { minCardWidth = 260, maxColumns = 4, gap = 16 } = options;
3
+ if (!Number.isFinite(width) || width < 0 || !Number.isFinite(minCardWidth) || minCardWidth <= 0 || !Number.isInteger(maxColumns) || maxColumns < 1 || !Number.isFinite(gap) || gap < 0)
4
+ throw new RangeError('Invalid responsive grid dimensions');
5
+ const columns = Math.max(1, Math.min(maxColumns, Math.floor((width + gap) / (minCardWidth + gap))));
6
+ return Object.freeze({ columns, gap, cardWidth: Math.max(0, (width - gap * (columns - 1)) / columns) });
7
+ }
8
+ export function hyResponsiveWorkspace(width, height, options = {}) {
9
+ const { minPaneWidth = 420, gap = 16 } = options;
10
+ if (![width, height, gap].every(v => Number.isFinite(v) && v >= 0) || !Number.isFinite(minPaneWidth) || minPaneWidth <= 0)
11
+ throw new RangeError('Invalid responsive workspace dimensions');
12
+ const split = width >= minPaneWidth * 2 + gap && height >= 420;
13
+ return Object.freeze({ split, gap, referenceHeight: Math.min(280, Math.max(0, height - 120), Math.max(120, Math.round(height * .38))) });
14
+ }
15
+ //# sourceMappingURL=responsive.js.map
@@ -8,12 +8,17 @@ export declare const HY_THEME_TOKENS: Readonly<{
8
8
  readonly danger: "--hy-invalid-border-color";
9
9
  readonly border: "--hy-border-color";
10
10
  readonly focus: "--hy-focus-ring-color";
11
+ readonly selected: "--hy-selected-bg-color";
12
+ readonly pressed: "--hy-pressed-bg-color";
13
+ readonly disabled: "--hy-disabled-text-color";
14
+ readonly elevated: "--hy-surface-elevated-color";
15
+ readonly onAccent: "--hy-on-accent-color";
11
16
  readonly fontSize: "--hy-font-size";
12
17
  readonly lineHeight: "--hy-line-height";
13
18
  readonly gap: "--hy-gap";
14
19
  readonly radius: "--hy-radius";
15
20
  }>;
16
- export type HYThemeVariables = Readonly<Record<(typeof HY_THEME_TOKENS)[keyof typeof HY_THEME_TOKENS], string>>;
21
+ export type HYThemeVariables = Readonly<Record<(typeof HY_THEME_TOKENS)['text' | 'muted' | 'surface' | 'input' | 'accent' | 'danger' | 'border' | 'focus' | 'fontSize' | 'lineHeight' | 'gap' | 'radius'], string> & Partial<Record<(typeof HY_THEME_TOKENS)[keyof typeof HY_THEME_TOKENS], string>> & Record<string, string>>;
17
22
  export declare function hyNativeTheme(theme: HYTheme, fontScale?: number): HYTheme;
18
23
  /** Returns data for an adapter to apply; importing it never creates a stylesheet. */
19
24
  export declare function hyThemeVariables(theme: HYTheme, fontScale?: number): HYThemeVariables;
@@ -3,6 +3,7 @@ import { snapshot } from '../core/data.js';
3
3
  export const HY_THEME_TOKENS = Object.freeze({
4
4
  text: '--hy-text-color', muted: '--hy-secondary-text-color', surface: '--hy-surface-color', input: '--hy-input-bg-color',
5
5
  accent: '--hy-accent-color', danger: '--hy-invalid-border-color', border: '--hy-border-color', focus: '--hy-focus-ring-color',
6
+ selected: '--hy-selected-bg-color', pressed: '--hy-pressed-bg-color', disabled: '--hy-disabled-text-color', elevated: '--hy-surface-elevated-color', onAccent: '--hy-on-accent-color',
6
7
  fontSize: '--hy-font-size', lineHeight: '--hy-line-height', gap: '--hy-gap', radius: '--hy-radius',
7
8
  });
8
9
  export function hyNativeTheme(theme, fontScale = 1) {
@@ -10,7 +11,7 @@ export function hyNativeTheme(theme, fontScale = 1) {
10
11
  if (!theme || typeof theme !== 'object')
11
12
  bad('theme');
12
13
  for (const key of Object.keys(theme))
13
- if (!['color', 'fontSize', 'lineHeight', 'gap', 'radius'].includes(key))
14
+ if (!['color', 'fontSize', 'lineHeight', 'gap', 'radius', 'typography'].includes(key))
14
15
  bad(key);
15
16
  if (!Number.isFinite(fontScale) || fontScale <= 0)
16
17
  bad('fontScale');
@@ -23,11 +24,29 @@ export function hyNativeTheme(theme, fontScale = 1) {
23
24
  bad(`color.${key}`);
24
25
  }
25
26
  for (const key of Object.keys(theme.color))
26
- if (!['text', 'muted', 'surface', 'input', 'accent', 'danger', 'border', 'focus'].includes(key))
27
+ if (!['text', 'muted', 'surface', 'input', 'accent', 'danger', 'border', 'focus', 'selected', 'pressed', 'disabled', 'elevated', 'onAccent'].includes(key))
27
28
  bad(`color.${key}`);
29
+ for (const [key, value] of Object.entries(theme.color))
30
+ if (typeof value !== 'string' || !/^#[\da-f]{6}([\da-f]{2})?$/i.test(value))
31
+ bad(`color.${key}`);
32
+ const typography = {};
33
+ if (theme.typography !== undefined) {
34
+ if (!theme.typography || typeof theme.typography !== 'object' || Array.isArray(theme.typography))
35
+ bad('typography');
36
+ for (const [key, value] of Object.entries(theme.typography)) {
37
+ if (!['title', 'body', 'caption'].includes(key) || !value || typeof value !== 'object')
38
+ bad(`typography.${key}`);
39
+ for (const field of Object.keys(value))
40
+ if (!['fontSize', 'lineHeight', 'weight'].includes(field))
41
+ bad(`typography.${key}.${field}`);
42
+ if (![value.fontSize, value.lineHeight].every(v => Number.isFinite(v) && v > 0 && Number.isFinite(v * fontScale)) || (value.weight !== undefined && !['normal', 'medium', 'bold'].includes(value.weight)))
43
+ bad(`typography.${key}`);
44
+ typography[key] = { ...value, fontSize: value.fontSize * fontScale, lineHeight: value.lineHeight * fontScale };
45
+ }
46
+ }
28
47
  if (!Number.isFinite(theme.fontSize * fontScale) || !Number.isFinite(theme.lineHeight * fontScale))
29
48
  bad('fontScale');
30
- return snapshot({ color: { ...theme.color }, fontSize: theme.fontSize * fontScale, lineHeight: theme.lineHeight * fontScale, gap: theme.gap, radius: theme.radius });
49
+ return snapshot({ color: { ...theme.color }, fontSize: theme.fontSize * fontScale, lineHeight: theme.lineHeight * fontScale, gap: theme.gap, radius: theme.radius, ...(theme.typography ? { typography } : {}) });
31
50
  }
32
51
  /** Returns data for an adapter to apply; importing it never creates a stylesheet. */
33
52
  export function hyThemeVariables(theme, fontScale = 1) {
@@ -35,6 +54,7 @@ export function hyThemeVariables(theme, fontScale = 1) {
35
54
  return Object.freeze(Object.fromEntries([
36
55
  ...Object.entries(data.color).map(([key, value]) => [HY_THEME_TOKENS[key], value]),
37
56
  ...['fontSize', 'lineHeight', 'gap', 'radius'].map(key => [HY_THEME_TOKENS[key], `${data[key]}px`]),
57
+ ...Object.entries(data.typography ?? {}).flatMap(([key, value]) => [[`--hy-${key}-font-size`, `${value.fontSize}px`], [`--hy-${key}-line-height`, `${value.lineHeight}px`], [`--hy-${key}-font-weight`, value.weight === 'bold' ? '700' : value.weight === 'medium' ? '500' : '400']]),
38
58
  ]));
39
59
  }
40
60
  //# sourceMappingURL=theme.js.map
@@ -0,0 +1,62 @@
1
+ export type HYQRCodeErrorCorrectionLevel = 'L' | 'M' | 'Q' | 'H';
2
+ export type HYQRCodeStatus = 'empty' | 'ready' | 'error';
3
+ export interface HYQRCodeRenderDetail {
4
+ readonly value: string;
5
+ readonly version: number;
6
+ readonly moduleCount: number;
7
+ readonly errorCorrectionLevel: HYQRCodeErrorCorrectionLevel;
8
+ }
9
+ export interface HYQRCodeErrorDetail {
10
+ readonly message: string;
11
+ }
12
+ export interface HYQRCodeIconErrorDetail {
13
+ readonly src: string;
14
+ }
15
+ /** Local SVG QR generation with a bounded decorative image or named icon slot. */
16
+ export declare class HYQRCode extends HTMLElement {
17
+ private readonly _frame;
18
+ private readonly _svg;
19
+ private readonly _background;
20
+ private readonly _modules;
21
+ private readonly _iconFrame;
22
+ private readonly _image;
23
+ private readonly _slot;
24
+ private readonly _message;
25
+ private _code;
26
+ private _key;
27
+ private _pathKey;
28
+ private _lastIcon;
29
+ private _iconReady;
30
+ private _state;
31
+ private _error;
32
+ static get observedAttributes(): string[];
33
+ constructor();
34
+ connectedCallback(): void;
35
+ disconnectedCallback(): void;
36
+ attributeChangedCallback(): void;
37
+ get value(): string;
38
+ set value(value: string);
39
+ get size(): number;
40
+ set size(value: number);
41
+ get color(): string;
42
+ set color(value: string);
43
+ get backgroundColor(): string;
44
+ set backgroundColor(value: string);
45
+ /** Quiet zone in modules. At least four modules are retained. */
46
+ get margin(): number;
47
+ set margin(value: number);
48
+ /** Defaults to H with an icon and M otherwise. An explicit level takes precedence. */
49
+ get errorCorrectionLevel(): HYQRCodeErrorCorrectionLevel;
50
+ set errorCorrectionLevel(value: HYQRCodeErrorCorrectionLevel);
51
+ get icon(): string;
52
+ set icon(value: string);
53
+ /** Requested image/slot size in CSS pixels. Actual coverage is bounded for readability. */
54
+ get iconSize(): number;
55
+ set iconSize(value: number);
56
+ get iconMargin(): number;
57
+ set iconMargin(value: number);
58
+ get status(): HYQRCodeStatus;
59
+ private _hasSlotIcon;
60
+ private _render;
61
+ }
62
+ export declare function defineQRCodeComponents(): void;
@@ -0,0 +1,224 @@
1
+ import { qrcodegen } from './internal/qrcodegen.js';
2
+ const levels = { L: qrcodegen.QrCode.Ecc.LOW, M: qrcodegen.QrCode.Ecc.MEDIUM, Q: qrcodegen.QrCode.Ecc.QUARTILE, H: qrcodegen.QrCode.Ecc.HIGH };
3
+ function integer(value, fallback, min, max) {
4
+ if (value == null || value === '')
5
+ return fallback;
6
+ const number = Number(value);
7
+ return Number.isFinite(number) ? Math.min(max, Math.max(min, Math.floor(number))) : fallback;
8
+ }
9
+ /** Local SVG QR generation with a bounded decorative image or named icon slot. */
10
+ export class HYQRCode extends HTMLElement {
11
+ _frame = document.createElement('div');
12
+ _svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
13
+ _background = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
14
+ _modules = document.createElementNS('http://www.w3.org/2000/svg', 'path');
15
+ _iconFrame = document.createElement('div');
16
+ _image = document.createElement('img');
17
+ _slot = document.createElement('slot');
18
+ _message = document.createElement('span');
19
+ _code = null;
20
+ _key = '';
21
+ _pathKey = '';
22
+ _lastIcon = '';
23
+ _iconReady = false;
24
+ _state = 'empty';
25
+ _error = '';
26
+ static get observedAttributes() {
27
+ return ['value', 'size', 'color', 'background-color', 'margin', 'error-correction-level', 'icon', 'icon-size', 'icon-margin', 'aria-label'];
28
+ }
29
+ constructor() {
30
+ super();
31
+ const root = this.attachShadow({ mode: 'open' });
32
+ const style = document.createElement('style');
33
+ style.textContent = `
34
+ :host { display: inline-block; max-width: 100%; vertical-align: middle; color: var(--hy-text-color, #eef0ff); font: 12px/1.5 system-ui, sans-serif; }
35
+ :host([hidden]), [hidden] { display: none !important; }
36
+ .frame { position: relative; box-sizing: border-box; max-width: 100%; aspect-ratio: 1; }
37
+ svg { display: block; width: 100%; height: 100%; }
38
+ .icon { position: absolute; box-sizing: border-box; inset: 50% auto auto 50%; transform: translate(-50%, -50%); display: grid; place-items: center; background: var(--_qr-background); border-radius: var(--hy-qr-code-icon-radius, 4px); pointer-events: none; overflow: hidden; }
39
+ img { display: block; width: 100%; height: 100%; min-width: 0; min-height: 0; object-fit: contain; }
40
+ slot { display: contents; }
41
+ ::slotted([slot="icon"]) { display: block; box-sizing: border-box; width: 100%; height: 100%; min-width: 0; min-height: 0; object-fit: contain; overflow: hidden; }
42
+ .message { position: absolute; inset: 0; display: grid; place-items: center; box-sizing: border-box; padding: 12px; text-align: center; color: var(--hy-secondary-text-color, #a9add4); background: var(--hy-surface-color, #10132f); border: 1px solid var(--hy-border-color, #343765); border-radius: var(--hy-qr-code-radius, 8px); }
43
+ `;
44
+ this._frame.className = 'frame';
45
+ this._frame.part.add('frame');
46
+ this._svg.part.add('code');
47
+ this._svg.setAttribute('aria-hidden', 'true');
48
+ this._svg.setAttribute('shape-rendering', 'crispEdges');
49
+ this._background.part.add('background');
50
+ this._modules.part.add('modules');
51
+ this._svg.append(this._background, this._modules);
52
+ this._iconFrame.className = 'icon';
53
+ this._iconFrame.part.add('icon');
54
+ this._iconFrame.setAttribute('aria-hidden', 'true');
55
+ this._iconFrame.inert = true;
56
+ this._image.alt = '';
57
+ this._image.draggable = false;
58
+ this._image.part.add('icon-image');
59
+ this._slot.name = 'icon';
60
+ this._iconFrame.append(this._slot, this._image);
61
+ this._message.className = 'message';
62
+ this._message.part.add('message');
63
+ this._frame.append(this._svg, this._iconFrame, this._message);
64
+ root.append(style, this._frame);
65
+ this._image.addEventListener('load', () => {
66
+ if (!this.isConnected || !this._image.naturalWidth)
67
+ return;
68
+ this._iconReady = true;
69
+ this._render();
70
+ });
71
+ this._image.addEventListener('error', () => {
72
+ if (!this.isConnected || !this._lastIcon)
73
+ return;
74
+ this._iconReady = false;
75
+ this._render();
76
+ this.dispatchEvent(new CustomEvent('qr-icon-error', { detail: { src: this._lastIcon }, bubbles: true, composed: true }));
77
+ });
78
+ }
79
+ connectedCallback() { this._slot.addEventListener('slotchange', this._render); this._render(); }
80
+ disconnectedCallback() {
81
+ this._slot.removeEventListener('slotchange', this._render);
82
+ this._lastIcon = '';
83
+ this._iconReady = false;
84
+ this._image.removeAttribute('src');
85
+ }
86
+ attributeChangedCallback() { this._render(); }
87
+ get value() { return this.getAttribute('value') ?? ''; }
88
+ set value(value) { this.setAttribute('value', String(value ?? '')); }
89
+ get size() { return integer(this.getAttribute('size'), 200, 64, 2048); }
90
+ set size(value) { this.setAttribute('size', String(integer(value, 200, 64, 2048))); }
91
+ get color() { return this.getAttribute('color') ?? ''; }
92
+ set color(value) { this.setAttribute('color', value); }
93
+ get backgroundColor() { return this.getAttribute('background-color') ?? ''; }
94
+ set backgroundColor(value) { this.setAttribute('background-color', value); }
95
+ /** Quiet zone in modules. At least four modules are retained. */
96
+ get margin() { return integer(this.getAttribute('margin'), 4, 4, 16); }
97
+ set margin(value) { this.setAttribute('margin', String(integer(value, 4, 4, 16))); }
98
+ /** Defaults to H with an icon and M otherwise. An explicit level takes precedence. */
99
+ get errorCorrectionLevel() {
100
+ const value = this.getAttribute('error-correction-level')?.toUpperCase();
101
+ if (value === 'L' || value === 'M' || value === 'Q' || value === 'H')
102
+ return value;
103
+ return this.icon || this._hasSlotIcon() ? 'H' : 'M';
104
+ }
105
+ set errorCorrectionLevel(value) { this.setAttribute('error-correction-level', value); }
106
+ get icon() { return this.getAttribute('icon') ?? ''; }
107
+ set icon(value) { this.setAttribute('icon', value ?? ''); }
108
+ /** Requested image/slot size in CSS pixels. Actual coverage is bounded for readability. */
109
+ get iconSize() { return integer(this.getAttribute('icon-size'), 32, 4, 512); }
110
+ set iconSize(value) { this.setAttribute('icon-size', String(integer(value, 32, 4, 512))); }
111
+ get iconMargin() { return integer(this.getAttribute('icon-margin'), 2, 0, 64); }
112
+ set iconMargin(value) { this.setAttribute('icon-margin', String(integer(value, 2, 0, 64))); }
113
+ get status() { return this._state; }
114
+ _hasSlotIcon() {
115
+ return this._slot.assignedNodes().some(node => node.nodeType === Node.ELEMENT_NODE || Boolean(node.textContent?.trim()));
116
+ }
117
+ _render = () => {
118
+ if (!this.isConnected)
119
+ return;
120
+ const value = this.value;
121
+ const level = this.errorCorrectionLevel;
122
+ const key = JSON.stringify([value, level]);
123
+ let generated = false;
124
+ let failed = false;
125
+ if (key !== this._key) {
126
+ this._key = key;
127
+ this._code = null;
128
+ this._error = '';
129
+ this._state = value ? 'ready' : 'empty';
130
+ if (value) {
131
+ try {
132
+ const bytes = new TextEncoder().encode(value);
133
+ if (bytes.length > 7089)
134
+ throw new RangeError('Value too long');
135
+ const segments = /^[\x00-\x7f]*$/.test(value) ? qrcodegen.QrSegment.makeSegments(value) : [qrcodegen.QrSegment.makeEci(26), qrcodegen.QrSegment.makeBytes([...bytes])];
136
+ this._code = qrcodegen.QrCode.encodeSegments(segments, levels[level], 1, 40, -1, false);
137
+ generated = true;
138
+ }
139
+ catch {
140
+ this._state = 'error';
141
+ this._error = 'This value is too long for the selected error correction level.';
142
+ failed = true;
143
+ }
144
+ }
145
+ }
146
+ const background = CSS.supports('color', this.backgroundColor) ? this.backgroundColor : 'var(--hy-qr-code-background-color, #ffffff)';
147
+ const color = CSS.supports('color', this.color) ? this.color : 'var(--hy-qr-code-color, #111827)';
148
+ this._frame.style.width = `${this.size}px`;
149
+ this._frame.style.setProperty('--_qr-background', background);
150
+ this._frame.dataset.state = this._state;
151
+ this._svg.toggleAttribute('hidden', this._state !== 'ready');
152
+ this._message.hidden = this._state === 'ready';
153
+ this._message.textContent = this._state === 'error' ? this._error : 'Enter a value to generate a QR code.';
154
+ this._message.setAttribute('role', this._state === 'error' ? 'alert' : 'status');
155
+ if (this._state === 'ready') {
156
+ this._frame.setAttribute('role', 'img');
157
+ this._frame.setAttribute('aria-label', this.getAttribute('aria-label') ?? 'QR code');
158
+ }
159
+ else {
160
+ this._frame.removeAttribute('role');
161
+ this._frame.removeAttribute('aria-label');
162
+ }
163
+ this._background.setAttribute('fill', background);
164
+ this._modules.setAttribute('fill', color);
165
+ const code = this._code;
166
+ const pathKey = `${key}:${this.margin}`;
167
+ if (!code) {
168
+ this._modules.removeAttribute('d');
169
+ this._pathKey = '';
170
+ }
171
+ else if (pathKey !== this._pathKey) {
172
+ this._pathKey = pathKey;
173
+ const extent = code.size + this.margin * 2;
174
+ this._svg.setAttribute('viewBox', `0 0 ${extent} ${extent}`);
175
+ this._background.setAttribute('width', String(extent));
176
+ this._background.setAttribute('height', String(extent));
177
+ const path = [];
178
+ for (let y = 0; y < code.size; y++) {
179
+ for (let x = 0; x < code.size;) {
180
+ if (!code.getModule(x, y)) {
181
+ x++;
182
+ continue;
183
+ }
184
+ const start = x;
185
+ while (x < code.size && code.getModule(x, y))
186
+ x++;
187
+ path.push(`M${start + this.margin},${y + this.margin}h${x - start}v1h-${x - start}z`);
188
+ }
189
+ }
190
+ this._modules.setAttribute('d', path.join(''));
191
+ }
192
+ const slotted = this._hasSlotIcon();
193
+ const src = slotted || !code ? '' : this.icon;
194
+ if (src !== this._lastIcon) {
195
+ this._lastIcon = src;
196
+ this._iconReady = false;
197
+ if (src)
198
+ this._image.src = src;
199
+ else
200
+ this._image.removeAttribute('src');
201
+ }
202
+ this._image.hidden = slotted || !src;
203
+ this._iconFrame.hidden = !code || (!slotted && !this._iconReady);
204
+ if (code) {
205
+ // Include the backing margin in the coverage limit; percentages also
206
+ // preserve that limit when max-width shrinks the rendered code.
207
+ const coverage = Math.min(this.iconSize + this.iconMargin * 2, this.size * code.size / (code.size + this.margin * 2) * .2);
208
+ const padding = Math.min(this.iconMargin, coverage / 4);
209
+ this._iconFrame.style.width = this._iconFrame.style.height = `${coverage / this.size * 100}%`;
210
+ this._iconFrame.style.padding = `${padding / this.size * 100}%`;
211
+ }
212
+ if (generated && code)
213
+ this.dispatchEvent(new CustomEvent('qr-render', {
214
+ detail: { value, version: code.version, moduleCount: code.size, errorCorrectionLevel: level }, bubbles: true, composed: true,
215
+ }));
216
+ if (failed)
217
+ this.dispatchEvent(new CustomEvent('qr-error', { detail: { message: this._error }, bubbles: true, composed: true }));
218
+ };
219
+ }
220
+ export function defineQRCodeComponents() {
221
+ if (!customElements.get('hy-qr-code'))
222
+ customElements.define('hy-qr-code', HYQRCode);
223
+ }
224
+ //# sourceMappingURL=qr-code.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haiyue/ui",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "Dependency-free Web Components for HaiyueStudio tools and products.",
6
6
  "license": "MIT",
@@ -57,6 +57,10 @@
57
57
  "types": "./dist/collapse.d.ts",
58
58
  "import": "./dist/collapse.js"
59
59
  },
60
+ "./carousel": {
61
+ "types": "./dist/carousel.d.ts",
62
+ "import": "./dist/carousel.js"
63
+ },
60
64
  "./cascader": {
61
65
  "types": "./dist/cascader.d.ts",
62
66
  "import": "./dist/cascader.js"
@@ -125,6 +129,10 @@
125
129
  "types": "./dist/table.d.ts",
126
130
  "import": "./dist/table.js"
127
131
  },
132
+ "./qr-code": {
133
+ "types": "./dist/qr-code.d.ts",
134
+ "import": "./dist/qr-code.js"
135
+ },
128
136
  "./radio": {
129
137
  "types": "./dist/radio.d.ts",
130
138
  "import": "./dist/radio.js"
@@ -149,6 +157,14 @@
149
157
  "types": "./dist/skeleton.d.ts",
150
158
  "import": "./dist/skeleton.js"
151
159
  },
160
+ "./modal": {
161
+ "types": "./dist/modal.d.ts",
162
+ "import": "./dist/modal.js"
163
+ },
164
+ "./masonry": {
165
+ "types": "./dist/masonry.d.ts",
166
+ "import": "./dist/masonry.js"
167
+ },
152
168
  "./split": {
153
169
  "types": "./dist/split.d.ts",
154
170
  "import": "./dist/split.js"
@@ -244,6 +260,7 @@
244
260
  "@playwright/test": "^1.63.0",
245
261
  "@rollup/plugin-node-resolve": "^16.0.3",
246
262
  "codemirror": "^6.0.2",
263
+ "jsqr": "1.4.0",
247
264
  "rollup": "^4.63.4",
248
265
  "typescript": "^5.2.0"
249
266
  }