@antglobal/copilot-cards-web 1.0.3 → 1.0.5
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.
- package/README.md +9 -2
- package/dist/index.d.ts +45 -36
- package/dist/index.js +439 -187
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getBuiltinIcon, cloneJsonData, createLifecycleManager, materializeCard,
|
|
1
|
+
import { getBuiltinIcon, cloneJsonData, createLifecycleManager, materializeCard, createA2UIParameterResolver, createExpressionContext, resolveActionRef, replaceRootContents, resolveA2UIDeep, hasExpression, resolveExpression, resolveDeep, resolveExpressionValue, isBoundRenderTreeNode, runActionSteps, normalizeSchema, validateSchema, requiresBindingMaterialization, parseSchema, StreamingParser, StreamingEngine, findAffectedRepeatOwners, bindingTopologyFingerprint, findTemplateRepeatOwners, extractPartialSchema, runActionStep, registry } from '@antglobal/copilot-cards-core';
|
|
2
2
|
export { ActionRegistry, a2uiComponentToElement, a2uiToCommand, convertLegacySchema, createLifecycleManager, hasExpression, isA2UIEnvelope, isLegacySchema, normalizeSchema, parseSchema, registerActionHandler, registry, resolveActionRef, resolveDeep, resolveExpression, resolveExpressionValue, runActionStep, runActionSteps, validateSchema } from '@antglobal/copilot-cards-core';
|
|
3
3
|
import * as echarts from 'echarts/core';
|
|
4
4
|
import { LineChart, BarChart, PieChart, ScatterChart, FunnelChart, HeatmapChart } from 'echarts/charts';
|
|
@@ -96,6 +96,207 @@ function buildStyleString(styles) {
|
|
|
96
96
|
.join(';');
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** Reusable REM preset; applied only when passed explicitly as `responsive.mobile`. */
|
|
100
|
+
const DEFAULT_MOBILE_RESPONSIVE = Object.freeze({
|
|
101
|
+
unit: 'rem',
|
|
102
|
+
rootValue: 100,
|
|
103
|
+
});
|
|
104
|
+
function formatRem(value) {
|
|
105
|
+
return Number(value.toFixed(6)).toString();
|
|
106
|
+
}
|
|
107
|
+
function isIdentifierChar(value) {
|
|
108
|
+
if (value == null)
|
|
109
|
+
return false;
|
|
110
|
+
const codePoint = value.codePointAt(0) ?? 0;
|
|
111
|
+
return /[a-zA-Z0-9_-]/.test(value) || value === '\\' || codePoint >= 0x80;
|
|
112
|
+
}
|
|
113
|
+
function quotedEnd(value, start) {
|
|
114
|
+
const quote = value[start];
|
|
115
|
+
let cursor = start + 1;
|
|
116
|
+
while (cursor < value.length) {
|
|
117
|
+
if (value[cursor] === '\\') {
|
|
118
|
+
cursor += 2;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (value[cursor] === quote)
|
|
122
|
+
return cursor + 1;
|
|
123
|
+
cursor++;
|
|
124
|
+
}
|
|
125
|
+
return value.length;
|
|
126
|
+
}
|
|
127
|
+
function identifierEscape(value, start) {
|
|
128
|
+
if (value[start] !== '\\' || start + 1 >= value.length)
|
|
129
|
+
return undefined;
|
|
130
|
+
let cursor = start + 1;
|
|
131
|
+
if (/[\n\r\f]/.test(value[cursor]))
|
|
132
|
+
return undefined;
|
|
133
|
+
let hex = '';
|
|
134
|
+
while (cursor < value.length && hex.length < 6 && /[0-9a-f]/i.test(value[cursor])) {
|
|
135
|
+
hex += value[cursor];
|
|
136
|
+
cursor++;
|
|
137
|
+
}
|
|
138
|
+
if (hex) {
|
|
139
|
+
const codePoint = Number.parseInt(hex, 16);
|
|
140
|
+
if (/\s/.test(value[cursor] ?? ''))
|
|
141
|
+
cursor++;
|
|
142
|
+
return {
|
|
143
|
+
decoded: codePoint === 0 || codePoint > 0x10ffff
|
|
144
|
+
? '\uFFFD'
|
|
145
|
+
: String.fromCodePoint(codePoint),
|
|
146
|
+
end: cursor,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { decoded: value[cursor], end: cursor + 1 };
|
|
150
|
+
}
|
|
151
|
+
function urlEnd(value, start) {
|
|
152
|
+
if (value[start]?.toLowerCase() !== 'u' && value[start] !== '\\') {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
if (isIdentifierChar(value[start - 1]))
|
|
156
|
+
return undefined;
|
|
157
|
+
let cursor = start;
|
|
158
|
+
let name = '';
|
|
159
|
+
while (cursor < value.length) {
|
|
160
|
+
const char = value[cursor];
|
|
161
|
+
if (/[a-zA-Z0-9_-]/.test(char) || (char.codePointAt(0) ?? 0) >= 0x80) {
|
|
162
|
+
name += char;
|
|
163
|
+
cursor++;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const escaped = identifierEscape(value, cursor);
|
|
167
|
+
if (!escaped)
|
|
168
|
+
break;
|
|
169
|
+
name += escaped.decoded;
|
|
170
|
+
cursor = escaped.end;
|
|
171
|
+
}
|
|
172
|
+
if (name.toLowerCase() !== 'url')
|
|
173
|
+
return undefined;
|
|
174
|
+
while (/\s/.test(value[cursor] ?? ''))
|
|
175
|
+
cursor++;
|
|
176
|
+
if (value[cursor] !== '(')
|
|
177
|
+
return undefined;
|
|
178
|
+
cursor++;
|
|
179
|
+
let depth = 1;
|
|
180
|
+
while (cursor < value.length && depth > 0) {
|
|
181
|
+
const char = value[cursor];
|
|
182
|
+
if (char === '"' || char === "'") {
|
|
183
|
+
cursor = quotedEnd(value, cursor);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (char === '\\') {
|
|
187
|
+
cursor += 2;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (char === '(')
|
|
191
|
+
depth++;
|
|
192
|
+
if (char === ')')
|
|
193
|
+
depth--;
|
|
194
|
+
cursor++;
|
|
195
|
+
}
|
|
196
|
+
return cursor;
|
|
197
|
+
}
|
|
198
|
+
/** Convert CSS px lengths while preserving strings, comments, and URLs. */
|
|
199
|
+
function convertPixelTokens(value, rootValue) {
|
|
200
|
+
let result = '';
|
|
201
|
+
let cursor = 0;
|
|
202
|
+
while (cursor < value.length) {
|
|
203
|
+
const char = value[cursor];
|
|
204
|
+
if (char === '"' || char === "'") {
|
|
205
|
+
const end = quotedEnd(value, cursor);
|
|
206
|
+
result += value.slice(cursor, end);
|
|
207
|
+
cursor = end;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (value.startsWith('/*', cursor)) {
|
|
211
|
+
const closing = value.indexOf('*/', cursor + 2);
|
|
212
|
+
const end = closing < 0 ? value.length : closing + 2;
|
|
213
|
+
result += value.slice(cursor, end);
|
|
214
|
+
cursor = end;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const protectedUrlEnd = urlEnd(value, cursor);
|
|
218
|
+
if (protectedUrlEnd !== undefined) {
|
|
219
|
+
result += value.slice(cursor, protectedUrlEnd);
|
|
220
|
+
cursor = protectedUrlEnd;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const canStartPixel = /[\d.]/.test(char)
|
|
224
|
+
|| (char === '-' && /[\d.]/.test(value[cursor + 1] ?? ''));
|
|
225
|
+
const pixel = canStartPixel && !isIdentifierChar(value[cursor - 1])
|
|
226
|
+
? /^(-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)px/i.exec(value.slice(cursor))
|
|
227
|
+
: null;
|
|
228
|
+
const pixelEnd = pixel ? cursor + pixel[0].length : cursor;
|
|
229
|
+
if (pixel && !isIdentifierChar(value[pixelEnd])) {
|
|
230
|
+
result += `${formatRem(Number(pixel[1]) / rootValue)}rem`;
|
|
231
|
+
cursor += pixel[0].length;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
result += char;
|
|
235
|
+
cursor++;
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
function createResponsiveContext(isMobile, responsive) {
|
|
240
|
+
const mobile = responsive?.mobile;
|
|
241
|
+
const unit = mobile?.unit;
|
|
242
|
+
const rootValue = mobile?.rootValue ?? DEFAULT_MOBILE_RESPONSIVE.rootValue;
|
|
243
|
+
if (mobile && (!Number.isFinite(rootValue) || rootValue <= 0)) {
|
|
244
|
+
throw new Error('[renderCard] responsive.mobile.rootValue must be a positive number');
|
|
245
|
+
}
|
|
246
|
+
const active = isMobile && unit === 'rem';
|
|
247
|
+
const context = {
|
|
248
|
+
active,
|
|
249
|
+
rootValue,
|
|
250
|
+
resolveLength(value) {
|
|
251
|
+
if (typeof value === 'number') {
|
|
252
|
+
return active
|
|
253
|
+
? `${formatRem(value / rootValue)}rem`
|
|
254
|
+
: `${value}px`;
|
|
255
|
+
}
|
|
256
|
+
return active ? convertPixelTokens(value, rootValue) : value;
|
|
257
|
+
},
|
|
258
|
+
convertCSS(value) {
|
|
259
|
+
return active ? convertPixelTokens(value, rootValue) : value;
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
return Object.freeze(context);
|
|
263
|
+
}
|
|
264
|
+
/** Convert CSS declarations without touching element text or SVG geometry. */
|
|
265
|
+
function applyResponsiveStyles(root, context) {
|
|
266
|
+
if (!context.active)
|
|
267
|
+
return;
|
|
268
|
+
const isPreservedContentDescendant = (element) => {
|
|
269
|
+
const boundary = element.closest('[data-responsive-content="preserve"]');
|
|
270
|
+
if (boundary == null || boundary === element)
|
|
271
|
+
return false;
|
|
272
|
+
const boundaryBelongsToRoot = root instanceof Element
|
|
273
|
+
? root.contains(boundary)
|
|
274
|
+
: boundary.getRootNode() === root;
|
|
275
|
+
return boundaryBelongsToRoot;
|
|
276
|
+
};
|
|
277
|
+
const convertElement = (element) => {
|
|
278
|
+
if (isPreservedContentDescendant(element))
|
|
279
|
+
return;
|
|
280
|
+
if (element instanceof SVGElement
|
|
281
|
+
&& !element.classList.contains('icon-svg'))
|
|
282
|
+
return;
|
|
283
|
+
const cssText = element.getAttribute('style');
|
|
284
|
+
if (cssText?.includes('px')) {
|
|
285
|
+
element.setAttribute('style', context.convertCSS(cssText));
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
if (root instanceof HTMLElement)
|
|
289
|
+
convertElement(root);
|
|
290
|
+
root.querySelectorAll('[style]').forEach(convertElement);
|
|
291
|
+
root.querySelectorAll('style').forEach((style) => {
|
|
292
|
+
if (isPreservedContentDescendant(style))
|
|
293
|
+
return;
|
|
294
|
+
if (style.textContent?.includes('px')) {
|
|
295
|
+
style.textContent = context.convertCSS(style.textContent);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
99
300
|
/**
|
|
100
301
|
* Chart renderer using ECharts (tree-shaken import).
|
|
101
302
|
* Supports: line, bar, pie/donut, scatter, funnel, heatmap.
|
|
@@ -1447,7 +1648,7 @@ function renderTableSlot(container, slotContent) {
|
|
|
1447
1648
|
* common style helpers. Designed to be used by `renderCard` which
|
|
1448
1649
|
* passes resolved props via `setData()`.
|
|
1449
1650
|
*
|
|
1450
|
-
* Note: lifecycle management, event binding, and
|
|
1651
|
+
* Note: lifecycle management, event binding, and mobile-mode selection
|
|
1451
1652
|
* are handled externally by `renderCard` / the render pipeline.
|
|
1452
1653
|
* BaseElement keeps itself lightweight and focused on DOM rendering.
|
|
1453
1654
|
*/
|
|
@@ -1459,6 +1660,7 @@ class BaseElement extends HTMLElement {
|
|
|
1459
1660
|
this._node = null;
|
|
1460
1661
|
this._props = {};
|
|
1461
1662
|
this._isMobile = false;
|
|
1663
|
+
this._responsive = createResponsiveContext(false);
|
|
1462
1664
|
this.attachShadow({ mode: 'open' });
|
|
1463
1665
|
}
|
|
1464
1666
|
// ─── Data Interface ─────────────────────────────────────────
|
|
@@ -1466,10 +1668,12 @@ class BaseElement extends HTMLElement {
|
|
|
1466
1668
|
* Set component data and trigger render.
|
|
1467
1669
|
* Called by the component renderer (from `renderCard` pipeline).
|
|
1468
1670
|
*/
|
|
1469
|
-
setData(node, props, isMobile) {
|
|
1671
|
+
setData(node, props, isMobile, responsive) {
|
|
1470
1672
|
this._node = node;
|
|
1471
1673
|
this._props = props;
|
|
1472
1674
|
this._isMobile = isMobile;
|
|
1675
|
+
this._responsiveOptions = responsive;
|
|
1676
|
+
this._responsive = createResponsiveContext(isMobile, responsive);
|
|
1473
1677
|
// Expose identity on the host element for querying / debugging
|
|
1474
1678
|
this.setAttribute('data-card-id', node.id);
|
|
1475
1679
|
this.setAttribute('data-card-type', node.type);
|
|
@@ -1478,10 +1682,13 @@ class BaseElement extends HTMLElement {
|
|
|
1478
1682
|
/**
|
|
1479
1683
|
* Update props only (e.g. on variable change + re-render).
|
|
1480
1684
|
*/
|
|
1481
|
-
updateProps(props, isMobile) {
|
|
1685
|
+
updateProps(props, isMobile, responsive) {
|
|
1482
1686
|
this._props = props;
|
|
1483
1687
|
if (isMobile !== undefined)
|
|
1484
1688
|
this._isMobile = isMobile;
|
|
1689
|
+
if (responsive !== undefined)
|
|
1690
|
+
this._responsiveOptions = responsive;
|
|
1691
|
+
this._responsive = createResponsiveContext(this._isMobile, this._responsiveOptions);
|
|
1485
1692
|
this.render();
|
|
1486
1693
|
}
|
|
1487
1694
|
// ─── Helpers ────────────────────────────────────────────────
|
|
@@ -1501,11 +1708,18 @@ class BaseElement extends HTMLElement {
|
|
|
1501
1708
|
const processed = isExpressionResult
|
|
1502
1709
|
? style
|
|
1503
1710
|
: this.resolveSizeInStyle(style);
|
|
1504
|
-
return this.escapeAttribute(buildStyleString(processed));
|
|
1711
|
+
return this.escapeAttribute(this._responsive.convertCSS(buildStyleString(processed)));
|
|
1505
1712
|
}
|
|
1506
1713
|
/** Resolve a single size value (number → px). */
|
|
1507
1714
|
toCSS(value) {
|
|
1508
|
-
return
|
|
1715
|
+
return this._responsive.resolveLength(value);
|
|
1716
|
+
}
|
|
1717
|
+
/** Assign component markup and convert only its CSS declarations. */
|
|
1718
|
+
setShadowHTML(html) {
|
|
1719
|
+
if (!this.shadowRoot)
|
|
1720
|
+
return;
|
|
1721
|
+
this.shadowRoot.innerHTML = html;
|
|
1722
|
+
applyResponsiveStyles(this.shadowRoot, this._responsive);
|
|
1509
1723
|
}
|
|
1510
1724
|
/**
|
|
1511
1725
|
* Escape a value before interpolating it into a double-quoted HTML
|
|
@@ -1531,13 +1745,13 @@ class BaseElement extends HTMLElement {
|
|
|
1531
1745
|
// line-height is ambiguous: numbers < 4 are CSS multipliers
|
|
1532
1746
|
// (1.5 = 1.5×font-size), larger numbers keep the SDK-wide
|
|
1533
1747
|
// number→px convention (22 = 22px) for schema compat.
|
|
1534
|
-
resolved[key] = value < 4 ? value :
|
|
1748
|
+
resolved[key] = value < 4 ? value : this.toCSS(value);
|
|
1535
1749
|
}
|
|
1536
1750
|
else if (BaseElement.UNITLESS_PROPS.has(prop)) {
|
|
1537
1751
|
resolved[key] = value;
|
|
1538
1752
|
}
|
|
1539
1753
|
else {
|
|
1540
|
-
resolved[key] =
|
|
1754
|
+
resolved[key] = this.toCSS(value);
|
|
1541
1755
|
}
|
|
1542
1756
|
}
|
|
1543
1757
|
return resolved;
|
|
@@ -1600,7 +1814,7 @@ class CardText extends BaseElement {
|
|
|
1600
1814
|
this._tooltipTimer = 0;
|
|
1601
1815
|
}
|
|
1602
1816
|
// ─── setData override: reset streaming state on re-bindé ─────
|
|
1603
|
-
setData(node, props, isMobile) {
|
|
1817
|
+
setData(node, props, isMobile, responsive) {
|
|
1604
1818
|
// Reset streaming state so full re-render is triggered
|
|
1605
1819
|
if (this._streamTimer) {
|
|
1606
1820
|
clearInterval(this._streamTimer);
|
|
@@ -1611,12 +1825,7 @@ class CardText extends BaseElement {
|
|
|
1611
1825
|
this._displayedContent = '';
|
|
1612
1826
|
this._contentEl = null;
|
|
1613
1827
|
this._cursorEl = null;
|
|
1614
|
-
|
|
1615
|
-
this._props = props;
|
|
1616
|
-
this._isMobile = isMobile;
|
|
1617
|
-
this.setAttribute('data-card-id', node.id);
|
|
1618
|
-
this.setAttribute('data-card-type', node.type);
|
|
1619
|
-
this.render();
|
|
1828
|
+
super.setData(node, props, isMobile, responsive);
|
|
1620
1829
|
}
|
|
1621
1830
|
render() {
|
|
1622
1831
|
if (!this.shadowRoot || !this._node)
|
|
@@ -1670,7 +1879,7 @@ class CardText extends BaseElement {
|
|
|
1670
1879
|
: rawText;
|
|
1671
1880
|
// Use <span> for plain text (legacy compat), <div> for markdown (block elements)
|
|
1672
1881
|
const tag = isMarkdown || isStreaming ? 'div' : 'span';
|
|
1673
|
-
this.
|
|
1882
|
+
this.setShadowHTML(`
|
|
1674
1883
|
<style>
|
|
1675
1884
|
:host {
|
|
1676
1885
|
display: inline-block;
|
|
@@ -1782,8 +1991,8 @@ class CardText extends BaseElement {
|
|
|
1782
1991
|
<${tag}
|
|
1783
1992
|
class="card-text ${isMobile ? 'card-mobile' : 'card-desktop'}${maxLines ? ' clamped' : ''}"
|
|
1784
1993
|
style="${combinedStyle}"
|
|
1785
|
-
><span class="card-text-content">${initialHTML}</span>${isStreaming ? '<span class="streaming-cursor"></span>' : ''}</${tag}>
|
|
1786
|
-
|
|
1994
|
+
><span class="card-text-content" data-responsive-content="preserve">${initialHTML}</span>${isStreaming ? '<span class="streaming-cursor"></span>' : ''}</${tag}>
|
|
1995
|
+
`);
|
|
1787
1996
|
// Cache DOM references for streaming updates
|
|
1788
1997
|
this._contentEl = this.shadowRoot.querySelector('.card-text-content');
|
|
1789
1998
|
this._cursorEl = this.shadowRoot.querySelector('.streaming-cursor');
|
|
@@ -1907,7 +2116,12 @@ class CardText extends BaseElement {
|
|
|
1907
2116
|
* Remove cursor when streaming ends.
|
|
1908
2117
|
* Called externally when streaming prop changes to false.
|
|
1909
2118
|
*/
|
|
1910
|
-
updateProps(props, isMobile) {
|
|
2119
|
+
updateProps(props, isMobile, responsive) {
|
|
2120
|
+
if (isMobile !== undefined)
|
|
2121
|
+
this._isMobile = isMobile;
|
|
2122
|
+
if (responsive !== undefined)
|
|
2123
|
+
this._responsiveOptions = responsive;
|
|
2124
|
+
this._responsive = createResponsiveContext(this._isMobile, this._responsiveOptions);
|
|
1911
2125
|
const wasStreaming = this._props.streaming === true || this._props.streaming === 'true';
|
|
1912
2126
|
const willStream = props.streaming === true || props.streaming === 'true';
|
|
1913
2127
|
// If streaming just ended, remove cursor and stop animation
|
|
@@ -1928,13 +2142,9 @@ class CardText extends BaseElement {
|
|
|
1928
2142
|
: this._escapeHTML(rawText);
|
|
1929
2143
|
}
|
|
1930
2144
|
this._props = props;
|
|
1931
|
-
if (isMobile !== undefined)
|
|
1932
|
-
this._isMobile = isMobile;
|
|
1933
2145
|
return;
|
|
1934
2146
|
}
|
|
1935
2147
|
this._props = props;
|
|
1936
|
-
if (isMobile !== undefined)
|
|
1937
|
-
this._isMobile = isMobile;
|
|
1938
2148
|
this.render();
|
|
1939
2149
|
}
|
|
1940
2150
|
// ─── Helpers ──────────────────────────────────────────────────
|
|
@@ -2141,7 +2351,7 @@ class CardButton extends BaseElement {
|
|
|
2141
2351
|
large: { padding: '12px 28px', fontSize: '16px' },
|
|
2142
2352
|
};
|
|
2143
2353
|
const sizeConfig = sizeMap[size] ?? sizeMap.medium;
|
|
2144
|
-
this.
|
|
2354
|
+
this.setShadowHTML(`
|
|
2145
2355
|
<style>
|
|
2146
2356
|
:host {
|
|
2147
2357
|
display: ${block ? 'block' : 'inline-block'};
|
|
@@ -2286,7 +2496,7 @@ class CardButton extends BaseElement {
|
|
|
2286
2496
|
${disabled ? 'disabled' : ''}
|
|
2287
2497
|
style="${inlineStyle}"
|
|
2288
2498
|
><span class="card-btn-content">${icon ? `<span class="card-btn-icon">${sanitizeIconHtml(String(icon))}</span>` : ''}${displayText}</span></button>
|
|
2289
|
-
|
|
2499
|
+
`);
|
|
2290
2500
|
}
|
|
2291
2501
|
}
|
|
2292
2502
|
CardButton.is = 'ai-card-button';
|
|
@@ -2317,20 +2527,40 @@ function sanitizeImageSrc(src) {
|
|
|
2317
2527
|
return '';
|
|
2318
2528
|
return trimmed;
|
|
2319
2529
|
}
|
|
2530
|
+
function normalizeIconSize(value) {
|
|
2531
|
+
if (typeof value === 'number') {
|
|
2532
|
+
const size = Number.isFinite(value) && value > 0 ? value : 24;
|
|
2533
|
+
return { css: `${size}px`, intrinsic: size };
|
|
2534
|
+
}
|
|
2535
|
+
const raw = String(value ?? '').trim();
|
|
2536
|
+
if (/^(?:\d+\.?\d*|\.\d+)$/.test(raw)) {
|
|
2537
|
+
const size = Number(raw) || 24;
|
|
2538
|
+
return { css: `${size}px`, intrinsic: size };
|
|
2539
|
+
}
|
|
2540
|
+
if (/^(?:\d+\.?\d*|\.\d+)(?:px|rem|em|%|vw|vh|vmin|vmax|ch|ex|cm|mm|in|pt|pc)$/.test(raw)) {
|
|
2541
|
+
return { css: raw };
|
|
2542
|
+
}
|
|
2543
|
+
return { css: '24px', intrinsic: 24 };
|
|
2544
|
+
}
|
|
2320
2545
|
function renderIconContent(input) {
|
|
2321
|
-
const
|
|
2546
|
+
const normalizedSize = normalizeIconSize(input.size);
|
|
2547
|
+
const size = normalizedSize.css;
|
|
2322
2548
|
const name = input.name == null ? undefined : String(input.name);
|
|
2549
|
+
const sizeStyle = `width:${escapeAttr$1(size)};height:${escapeAttr$1(size)};`;
|
|
2550
|
+
const intrinsicAttrs = normalizedSize.intrinsic == null
|
|
2551
|
+
? ''
|
|
2552
|
+
: `width="${normalizedSize.intrinsic}" height="${normalizedSize.intrinsic}" `;
|
|
2323
2553
|
if (input.src) {
|
|
2324
2554
|
return `<img class="icon-img" src="${escapeAttr$1(sanitizeImageSrc(input.src))}" ` +
|
|
2325
|
-
|
|
2555
|
+
`${intrinsicAttrs}style="${sizeStyle}" alt="${escapeAttr$1(name ?? 'icon')}" />`;
|
|
2326
2556
|
}
|
|
2327
2557
|
const icon = getBuiltinIcon(name);
|
|
2328
2558
|
if (icon) {
|
|
2329
|
-
return `<svg class="icon-svg" viewBox="${escapeAttr$1(icon.viewBox)}"
|
|
2330
|
-
`
|
|
2559
|
+
return `<svg class="icon-svg" viewBox="${escapeAttr$1(icon.viewBox)}" ${intrinsicAttrs}style="${sizeStyle}" ` +
|
|
2560
|
+
`color="${escapeAttr$1(String(input.color ?? 'currentColor'))}" ` +
|
|
2331
2561
|
`aria-hidden="true">${icon.body}</svg>`;
|
|
2332
2562
|
}
|
|
2333
|
-
return `<span class="icon-text" style="font-size:${size}
|
|
2563
|
+
return `<span class="icon-text" style="font-size:${escapeAttr$1(size)};line-height:1;">` +
|
|
2334
2564
|
`${escapeText$1(name ?? '?')}</span>`;
|
|
2335
2565
|
}
|
|
2336
2566
|
|
|
@@ -2452,7 +2682,7 @@ class CardInput extends BaseElement {
|
|
|
2452
2682
|
render() {
|
|
2453
2683
|
if (!this.shadowRoot || !this._node)
|
|
2454
2684
|
return;
|
|
2455
|
-
const { placeholder = '', inputType = 'text', label, defaultValue = '', disabled = false, readonly: readOnly = false, maxLength, rows, min, max, step = 1, controls = true, prefix, suffix, style, inputStyle, isExpressionResultStyle, } = this._props;
|
|
2685
|
+
const { placeholder = '', autoFocus = false, inputType = 'text', label, defaultValue = '', disabled = false, readonly: readOnly = false, maxLength, rows, min, max, step = 1, controls = true, prefix, suffix, style, inputStyle, isExpressionResultStyle, } = this._props;
|
|
2456
2686
|
const inlineStyle = this.buildInlineStyle(style, isExpressionResultStyle);
|
|
2457
2687
|
const legacyInputStyle = style && typeof style === 'object' && style.resize != null
|
|
2458
2688
|
? { resize: style.resize }
|
|
@@ -2542,7 +2772,7 @@ class CardInput extends BaseElement {
|
|
|
2542
2772
|
${numberAttributes}
|
|
2543
2773
|
style="${nativeInlineStyle}"
|
|
2544
2774
|
/>`;
|
|
2545
|
-
this.
|
|
2775
|
+
this.setShadowHTML(`
|
|
2546
2776
|
<style>
|
|
2547
2777
|
:host {
|
|
2548
2778
|
display: block;
|
|
@@ -2716,7 +2946,7 @@ class CardInput extends BaseElement {
|
|
|
2716
2946
|
${numberStepperHtml}
|
|
2717
2947
|
</div>
|
|
2718
2948
|
</div>
|
|
2719
|
-
|
|
2949
|
+
`);
|
|
2720
2950
|
// Wire up native input/change events that bubble out of Shadow DOM.
|
|
2721
2951
|
// renderCard listens for these standard event names (mapped from onInput / onChange).
|
|
2722
2952
|
// The `detail.value` carries the current input value so that:
|
|
@@ -2738,6 +2968,14 @@ class CardInput extends BaseElement {
|
|
|
2738
2968
|
detail: { value: inputEl.value },
|
|
2739
2969
|
}));
|
|
2740
2970
|
});
|
|
2971
|
+
if (autoFocus && !disabled) {
|
|
2972
|
+
queueMicrotask(() => {
|
|
2973
|
+
const control = this.getAutoFocusControl();
|
|
2974
|
+
if (control && this.isFirstAvailableAutoFocusInput()) {
|
|
2975
|
+
control.focus();
|
|
2976
|
+
}
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2741
2979
|
if (isNumber &&
|
|
2742
2980
|
showNumberStepper &&
|
|
2743
2981
|
inputEl instanceof HTMLInputElement) {
|
|
@@ -2766,6 +3004,22 @@ class CardInput extends BaseElement {
|
|
|
2766
3004
|
}
|
|
2767
3005
|
}
|
|
2768
3006
|
// ─── Helpers ──────────────────────────────────────────────────
|
|
3007
|
+
getAutoFocusControl() {
|
|
3008
|
+
if (!this.isConnected
|
|
3009
|
+
|| !this._props.autoFocus
|
|
3010
|
+
|| this.hasAttribute('data-disabled')) {
|
|
3011
|
+
return null;
|
|
3012
|
+
}
|
|
3013
|
+
const control = this.shadowRoot?.querySelector('.card-input');
|
|
3014
|
+
return control && !control.disabled ? control : null;
|
|
3015
|
+
}
|
|
3016
|
+
isFirstAvailableAutoFocusInput() {
|
|
3017
|
+
const root = this.getRootNode();
|
|
3018
|
+
if (!('querySelectorAll' in root))
|
|
3019
|
+
return false;
|
|
3020
|
+
const firstAvailable = Array.from(root.querySelectorAll(CardInput.is)).find(input => input.getAutoFocusControl() !== null);
|
|
3021
|
+
return firstAvailable === this;
|
|
3022
|
+
}
|
|
2769
3023
|
/** Escape HTML entities for safe insertion. */
|
|
2770
3024
|
escapeHtml(str) {
|
|
2771
3025
|
return str
|
|
@@ -2839,7 +3093,7 @@ class CardImage extends BaseElement {
|
|
|
2839
3093
|
imgStyles.push(`object-fit:${objectFit}`);
|
|
2840
3094
|
if (inlineStyle)
|
|
2841
3095
|
imgStyles.push(inlineStyle);
|
|
2842
|
-
this.
|
|
3096
|
+
this.setShadowHTML(`
|
|
2843
3097
|
<style>
|
|
2844
3098
|
:host {
|
|
2845
3099
|
display: inline-block;
|
|
@@ -2948,7 +3202,7 @@ class CardImage extends BaseElement {
|
|
|
2948
3202
|
<button class="lightbox-close" aria-label="Close">×</button>
|
|
2949
3203
|
<img class="lightbox-image" src="${imgSrc}" alt="${imgAlt}" />
|
|
2950
3204
|
</div>
|
|
2951
|
-
|
|
3205
|
+
`);
|
|
2952
3206
|
this.bindEvents(preview);
|
|
2953
3207
|
}
|
|
2954
3208
|
bindEvents(preview) {
|
|
@@ -3040,7 +3294,7 @@ class CardDivider extends BaseElement {
|
|
|
3040
3294
|
const isVertical = direction === 'vertical';
|
|
3041
3295
|
const borderStyle = dashed ? 'dashed' : 'solid';
|
|
3042
3296
|
const displayText = text ? this.resolveContent(text) : '';
|
|
3043
|
-
this.
|
|
3297
|
+
this.setShadowHTML(`
|
|
3044
3298
|
<style>
|
|
3045
3299
|
:host {
|
|
3046
3300
|
display: ${isVertical ? 'inline-flex' : 'flex'};
|
|
@@ -3064,7 +3318,7 @@ class CardDivider extends BaseElement {
|
|
|
3064
3318
|
</style>
|
|
3065
3319
|
<div class="divider-line"></div>
|
|
3066
3320
|
${displayText ? `<span class="divider-text">${displayText}</span><div class="divider-line"></div>` : ''}
|
|
3067
|
-
|
|
3321
|
+
`);
|
|
3068
3322
|
if (inlineStyle) {
|
|
3069
3323
|
this.style.cssText += ';' + inlineStyle;
|
|
3070
3324
|
}
|
|
@@ -3176,7 +3430,7 @@ class CardRate extends BaseElement {
|
|
|
3176
3430
|
return `<span class="star ${isFull ? 'filled' : ''}" data-value="${starIndex}"
|
|
3177
3431
|
style="${starInline};${itemInline}">★</span>`;
|
|
3178
3432
|
}).join('');
|
|
3179
|
-
this.
|
|
3433
|
+
this.setShadowHTML(`
|
|
3180
3434
|
<style>
|
|
3181
3435
|
:host {
|
|
3182
3436
|
display: inline-flex;
|
|
@@ -3234,7 +3488,7 @@ class CardRate extends BaseElement {
|
|
|
3234
3488
|
aria-readonly="${Boolean(readOnly)}"
|
|
3235
3489
|
style="${inlineStyle}"
|
|
3236
3490
|
>${stars}</div>
|
|
3237
|
-
|
|
3491
|
+
`);
|
|
3238
3492
|
if (!interactive)
|
|
3239
3493
|
return;
|
|
3240
3494
|
// Bind click handlers
|
|
@@ -3383,7 +3637,7 @@ class CardCounter extends BaseElement {
|
|
|
3383
3637
|
const numInline = this.buildInlineStyle(valueStyle, isExpressionResultStyle);
|
|
3384
3638
|
const minusContent = this.renderIcon(minusIcon, this.defaultMinusGlyph(btnSize), btnSize);
|
|
3385
3639
|
const plusContent = this.renderIcon(plusIcon, this.defaultPlusGlyph(btnSize), btnSize);
|
|
3386
|
-
this.
|
|
3640
|
+
this.setShadowHTML(`
|
|
3387
3641
|
<style>
|
|
3388
3642
|
:host {
|
|
3389
3643
|
display: inline-flex;
|
|
@@ -3458,7 +3712,7 @@ class CardCounter extends BaseElement {
|
|
|
3458
3712
|
${plusDisabled ? 'disabled' : ''} aria-label="${this.escapeAttr(String(increaseAriaLabel))}"
|
|
3459
3713
|
style="${plusInline}">${plusContent}</button>
|
|
3460
3714
|
</div>
|
|
3461
|
-
|
|
3715
|
+
`);
|
|
3462
3716
|
if (disabled || readOnly)
|
|
3463
3717
|
return;
|
|
3464
3718
|
const minusBtn = this.shadowRoot.querySelector('.c-minus');
|
|
@@ -3573,7 +3827,7 @@ class CardTag extends BaseElement {
|
|
|
3573
3827
|
const bgColor = isCustomColor ? color + '1a' : preset.bg;
|
|
3574
3828
|
const borderColor = isCustomColor ? color : preset.border;
|
|
3575
3829
|
const textColor = isCustomColor ? color : preset.text;
|
|
3576
|
-
this.
|
|
3830
|
+
this.setShadowHTML(`
|
|
3577
3831
|
<style>
|
|
3578
3832
|
:host {
|
|
3579
3833
|
display: inline-flex;
|
|
@@ -3609,7 +3863,7 @@ class CardTag extends BaseElement {
|
|
|
3609
3863
|
${text}
|
|
3610
3864
|
${closable ? '<span class="close-btn">✕</span>' : ''}
|
|
3611
3865
|
</span>
|
|
3612
|
-
|
|
3866
|
+
`);
|
|
3613
3867
|
if (closable) {
|
|
3614
3868
|
this.shadowRoot.querySelector('.close-btn')?.addEventListener('click', () => {
|
|
3615
3869
|
this.dispatchEvent(new CustomEvent('close', {
|
|
@@ -3664,7 +3918,7 @@ class CardSelect extends BaseElement {
|
|
|
3664
3918
|
render() {
|
|
3665
3919
|
if (!this.shadowRoot || !this._node)
|
|
3666
3920
|
return;
|
|
3667
|
-
const { options = [], placeholder = '请选择', value: externalValue, defaultValue = '', disabled = false, readonly: readOnly = false, ariaLabel = placeholder, size = 'medium', variant = 'outlined', accentColor = '#1677ff', style, styles = {}, isExpressionResultStyle, } = this._props;
|
|
3921
|
+
const { options = [], placeholder = '请选择', value: externalValue, defaultValue = '', disabled = false, readonly: readOnly = false, ariaLabel = placeholder, size = 'medium', variant = 'outlined', accentColor = '#1677ff', arrowSize, style, styles = {}, isExpressionResultStyle, } = this._props;
|
|
3668
3922
|
const propValue = String(externalValue ?? defaultValue ?? '');
|
|
3669
3923
|
const resolvedSize = (['small', 'medium', 'large'].includes(size)
|
|
3670
3924
|
? size
|
|
@@ -3702,6 +3956,7 @@ class CardSelect extends BaseElement {
|
|
|
3702
3956
|
optionPadding: '5px 12px',
|
|
3703
3957
|
},
|
|
3704
3958
|
}[resolvedSize];
|
|
3959
|
+
const resolvedArrowSize = arrowSize ?? sizePreset.arrowSize;
|
|
3705
3960
|
const variantPreset = {
|
|
3706
3961
|
outlined: {
|
|
3707
3962
|
background: '#fff',
|
|
@@ -3725,13 +3980,14 @@ class CardSelect extends BaseElement {
|
|
|
3725
3980
|
this.applyHostStyles(style, isExpressionResultStyle, String(accentColor));
|
|
3726
3981
|
const triggerInline = this.buildInlineStyle(semanticStyles.trigger, isExpressionResultStyle);
|
|
3727
3982
|
const dropdownInline = this.buildInlineStyle(semanticStyles.dropdown, isExpressionResultStyle);
|
|
3983
|
+
const arrowInline = this.buildInlineStyle(semanticStyles.arrow, isExpressionResultStyle);
|
|
3728
3984
|
const selectedOption = options.find((o) => o.value === value);
|
|
3729
3985
|
const displayText = selectedOption ? selectedOption.label : placeholder;
|
|
3730
3986
|
const isPlaceholder = !selectedOption;
|
|
3731
3987
|
const openClass = this._open ? 'open' : '';
|
|
3732
3988
|
const arrowIcon = renderIconContent({
|
|
3733
3989
|
name: 'caret_down',
|
|
3734
|
-
size:
|
|
3990
|
+
size: this.toCSS(resolvedArrowSize),
|
|
3735
3991
|
color: 'currentColor',
|
|
3736
3992
|
});
|
|
3737
3993
|
const listboxId = `${this._node.id}-listbox`;
|
|
@@ -3740,7 +3996,7 @@ class CardSelect extends BaseElement {
|
|
|
3740
3996
|
&& this._activeIndex < options.length)
|
|
3741
3997
|
? `${this._node.id}-option-${this._activeIndex}`
|
|
3742
3998
|
: '';
|
|
3743
|
-
this.
|
|
3999
|
+
this.setShadowHTML(`
|
|
3744
4000
|
<style>
|
|
3745
4001
|
:host {
|
|
3746
4002
|
display: inline-block;
|
|
@@ -3797,8 +4053,8 @@ class CardSelect extends BaseElement {
|
|
|
3797
4053
|
flex: none;
|
|
3798
4054
|
align-items: center;
|
|
3799
4055
|
justify-content: center;
|
|
3800
|
-
width: ${
|
|
3801
|
-
height: ${
|
|
4056
|
+
width: ${this.toCSS(resolvedArrowSize)};
|
|
4057
|
+
height: ${this.toCSS(resolvedArrowSize)};
|
|
3802
4058
|
margin-left: 8px;
|
|
3803
4059
|
color: ${disabled ? '#bfbfbf' : '#999'};
|
|
3804
4060
|
transition: transform 0.2s;
|
|
@@ -3885,7 +4141,7 @@ class CardSelect extends BaseElement {
|
|
|
3885
4141
|
style="${triggerInline}"
|
|
3886
4142
|
>
|
|
3887
4143
|
<span class="select-text">${this.escapeText(String(displayText))}</span>
|
|
3888
|
-
<span class="select-arrow ${openClass}">${arrowIcon}</span>
|
|
4144
|
+
<span class="select-arrow ${openClass}" style="${arrowInline}">${arrowIcon}</span>
|
|
3889
4145
|
</div>
|
|
3890
4146
|
<div
|
|
3891
4147
|
class="dropdown ${openClass}"
|
|
@@ -3911,7 +4167,7 @@ class CardSelect extends BaseElement {
|
|
|
3911
4167
|
><span class="option-label">${this.escapeText(String(opt.label))}</span></div>`);
|
|
3912
4168
|
}).join('')}
|
|
3913
4169
|
</div>
|
|
3914
|
-
|
|
4170
|
+
`);
|
|
3915
4171
|
if (disabled)
|
|
3916
4172
|
return;
|
|
3917
4173
|
const trigger = this.shadowRoot.querySelector('.select-trigger');
|
|
@@ -3938,13 +4194,13 @@ class CardSelect extends BaseElement {
|
|
|
3938
4194
|
disconnectedCallback() {
|
|
3939
4195
|
document.removeEventListener('click', this._onDocClick);
|
|
3940
4196
|
}
|
|
3941
|
-
updateProps(props, isMobile) {
|
|
4197
|
+
updateProps(props, isMobile, responsive) {
|
|
3942
4198
|
if (Object.prototype.hasOwnProperty.call(props, 'value')
|
|
3943
4199
|
|| Object.prototype.hasOwnProperty.call(props, 'defaultValue')) {
|
|
3944
4200
|
this._localValue = null;
|
|
3945
4201
|
this._propValue = undefined;
|
|
3946
4202
|
}
|
|
3947
|
-
super.updateProps(props, isMobile);
|
|
4203
|
+
super.updateProps(props, isMobile, responsive);
|
|
3948
4204
|
}
|
|
3949
4205
|
_setOpen(open) {
|
|
3950
4206
|
if (this._open === open)
|
|
@@ -4092,7 +4348,7 @@ class CardPasscodeInput extends BaseElement {
|
|
|
4092
4348
|
autocomplete="one-time-code"
|
|
4093
4349
|
${disabled ? 'disabled' : ''}
|
|
4094
4350
|
/>`).join('');
|
|
4095
|
-
this.
|
|
4351
|
+
this.setShadowHTML(`
|
|
4096
4352
|
<style>
|
|
4097
4353
|
:host {
|
|
4098
4354
|
display: inline-flex;
|
|
@@ -4128,7 +4384,7 @@ class CardPasscodeInput extends BaseElement {
|
|
|
4128
4384
|
}
|
|
4129
4385
|
</style>
|
|
4130
4386
|
<div class="passcode-wrapper">${inputs}</div>
|
|
4131
|
-
|
|
4387
|
+
`);
|
|
4132
4388
|
if (disabled)
|
|
4133
4389
|
return;
|
|
4134
4390
|
const cells = this.shadowRoot.querySelectorAll('.passcode-cell');
|
|
@@ -4211,10 +4467,10 @@ class CardIcon extends BaseElement {
|
|
|
4211
4467
|
const iconContent = renderIconContent({
|
|
4212
4468
|
name,
|
|
4213
4469
|
src,
|
|
4214
|
-
size: iconSize,
|
|
4470
|
+
size: this.toCSS(iconSize),
|
|
4215
4471
|
color,
|
|
4216
4472
|
});
|
|
4217
|
-
this.
|
|
4473
|
+
this.setShadowHTML(`
|
|
4218
4474
|
<style>
|
|
4219
4475
|
:host {
|
|
4220
4476
|
display: inline-flex;
|
|
@@ -4237,7 +4493,7 @@ class CardIcon extends BaseElement {
|
|
|
4237
4493
|
}
|
|
4238
4494
|
</style>
|
|
4239
4495
|
<span class="icon-wrapper" style="${inlineStyle}">${iconContent}</span>
|
|
4240
|
-
|
|
4496
|
+
`);
|
|
4241
4497
|
}
|
|
4242
4498
|
}
|
|
4243
4499
|
CardIcon.is = 'ai-card-icon';
|
|
@@ -4319,7 +4575,7 @@ class CardForm extends BaseElement {
|
|
|
4319
4575
|
const fieldItems = fields
|
|
4320
4576
|
.map((field, index) => this._renderField(field, index))
|
|
4321
4577
|
.join('');
|
|
4322
|
-
this.
|
|
4578
|
+
this.setShadowHTML(`
|
|
4323
4579
|
<style>
|
|
4324
4580
|
:host {
|
|
4325
4581
|
display: block;
|
|
@@ -4479,13 +4735,13 @@ class CardForm extends BaseElement {
|
|
|
4479
4735
|
${fieldItems}
|
|
4480
4736
|
<button class="submit-btn" ${disabled ? 'disabled' : ''} style="${isHorizontal ? `margin-left: 84px;` : ''}">${submitText}</button>
|
|
4481
4737
|
</div>
|
|
4482
|
-
|
|
4738
|
+
`);
|
|
4483
4739
|
this._initializeSelectFields(fields, disabled);
|
|
4484
4740
|
this._initializeRateFields(fields, disabled);
|
|
4485
4741
|
this._bindEvents(fields, disabled);
|
|
4486
4742
|
}
|
|
4487
4743
|
_renderField(field, index) {
|
|
4488
|
-
const { name, label, type = 'text', placeholder = '', required = false, rules = [], prefix, suffix,
|
|
4744
|
+
const { name, label, type = 'text', placeholder = '', required = false, rules = [], prefix, suffix, length = 6, } = field;
|
|
4489
4745
|
const val = this._values[name] ?? '';
|
|
4490
4746
|
const error = this._errors[name] || '';
|
|
4491
4747
|
const errorClass = error ? 'error' : '';
|
|
@@ -4600,7 +4856,7 @@ class CardForm extends BaseElement {
|
|
|
4600
4856
|
events: undefined,
|
|
4601
4857
|
directives: undefined,
|
|
4602
4858
|
};
|
|
4603
|
-
selectElement.setData(node, props, this._isMobile);
|
|
4859
|
+
selectElement.setData(node, props, this._isMobile, this._responsiveOptions);
|
|
4604
4860
|
const label = selectElement
|
|
4605
4861
|
.closest('.form-field')
|
|
4606
4862
|
?.querySelector('.field-label');
|
|
@@ -4653,7 +4909,7 @@ class CardForm extends BaseElement {
|
|
|
4653
4909
|
events: undefined,
|
|
4654
4910
|
directives: undefined,
|
|
4655
4911
|
};
|
|
4656
|
-
rateElement.setData(node, props, this._isMobile);
|
|
4912
|
+
rateElement.setData(node, props, this._isMobile, this._responsiveOptions);
|
|
4657
4913
|
const label = rateElement
|
|
4658
4914
|
.closest('.form-field')
|
|
4659
4915
|
?.querySelector('.field-label');
|
|
@@ -4964,7 +5220,7 @@ class CardLoading extends BaseElement {
|
|
|
4964
5220
|
const displayText = text ? this.resolveContent(text) : '';
|
|
4965
5221
|
const px = typeof size === 'number' ? `${size}px` : size;
|
|
4966
5222
|
const dur = typeof duration === 'number' ? `${duration}s` : duration;
|
|
4967
|
-
this.
|
|
5223
|
+
this.setShadowHTML(`
|
|
4968
5224
|
<style>
|
|
4969
5225
|
:host {
|
|
4970
5226
|
display: inline-flex;
|
|
@@ -4993,7 +5249,7 @@ class CardLoading extends BaseElement {
|
|
|
4993
5249
|
</style>
|
|
4994
5250
|
<div class="spinner"></div>
|
|
4995
5251
|
${displayText ? `<span class="loading-text">${displayText}</span>` : ''}
|
|
4996
|
-
|
|
5252
|
+
`);
|
|
4997
5253
|
if (inlineStyle) {
|
|
4998
5254
|
this.style.cssText += ';' + inlineStyle;
|
|
4999
5255
|
}
|
|
@@ -5185,6 +5441,7 @@ class CardProgress extends BaseElement {
|
|
|
5185
5441
|
track.setAttribute('aria-valuetext', valueText);
|
|
5186
5442
|
}
|
|
5187
5443
|
this.shadowRoot.replaceChildren(styleElement, wrapper);
|
|
5444
|
+
applyResponsiveStyles(this.shadowRoot, this._responsive);
|
|
5188
5445
|
}
|
|
5189
5446
|
buildLegend(segments, position, style, isExpressionResultStyle) {
|
|
5190
5447
|
const labeled = segments.filter((segment) => segment.label?.trim());
|
|
@@ -5378,7 +5635,7 @@ class CardSteps extends BaseElement {
|
|
|
5378
5635
|
</div>
|
|
5379
5636
|
`;
|
|
5380
5637
|
}).join('');
|
|
5381
|
-
this.
|
|
5638
|
+
this.setShadowHTML(`
|
|
5382
5639
|
<style>
|
|
5383
5640
|
:host {
|
|
5384
5641
|
display: block;
|
|
@@ -5483,7 +5740,7 @@ class CardSteps extends BaseElement {
|
|
|
5483
5740
|
}
|
|
5484
5741
|
</style>
|
|
5485
5742
|
<div class="steps-wrapper" style="${inlineStyle}">${stepsHTML}</div>
|
|
5486
|
-
|
|
5743
|
+
`);
|
|
5487
5744
|
// Bind click only on clickable steps → dispatch `step-click` event
|
|
5488
5745
|
this.shadowRoot.querySelectorAll('.step-clickable').forEach((el) => {
|
|
5489
5746
|
el.addEventListener('click', (e) => {
|
|
@@ -5577,10 +5834,12 @@ class CardCollapse extends BaseElement {
|
|
|
5577
5834
|
// Arrow HTML: custom image or built-in SVG
|
|
5578
5835
|
const arrowHTML = arrowIconUrl
|
|
5579
5836
|
? `<img src="${arrowIconUrl}" width="7" height="7" style="display:block" />`
|
|
5580
|
-
:
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5837
|
+
: renderIconContent({
|
|
5838
|
+
name: 'caret_down',
|
|
5839
|
+
size: this.toCSS(12),
|
|
5840
|
+
color: 'currentColor',
|
|
5841
|
+
});
|
|
5842
|
+
this.setShadowHTML(`
|
|
5584
5843
|
<style>
|
|
5585
5844
|
:host {
|
|
5586
5845
|
display: block;
|
|
@@ -5703,11 +5962,13 @@ class CardCollapse extends BaseElement {
|
|
|
5703
5962
|
</span>
|
|
5704
5963
|
</div>
|
|
5705
5964
|
<div class="collapse-body ${this._expanded ? 'expanded' : ''}" style="${bodyInlineStyle}">
|
|
5706
|
-
<div class="collapse-content" style="${contentInlineStyle}"
|
|
5965
|
+
<div class="collapse-content" style="${contentInlineStyle}">
|
|
5966
|
+
<div class="collapse-markdown" data-responsive-content="preserve">${bodyHTML ? bodyHTML : ''}</div>
|
|
5967
|
+
</div>
|
|
5707
5968
|
<div class="collapse-slot"><slot></slot></div>
|
|
5708
5969
|
</div>
|
|
5709
5970
|
</div>
|
|
5710
|
-
|
|
5971
|
+
`);
|
|
5711
5972
|
// Bind click event
|
|
5712
5973
|
const header = this.shadowRoot.querySelector('.collapse-header');
|
|
5713
5974
|
header?.addEventListener('click', () => this._toggle());
|
|
@@ -5879,7 +6140,7 @@ class CardHtml extends BaseElement {
|
|
|
5879
6140
|
}
|
|
5880
6141
|
if (inlineStyle)
|
|
5881
6142
|
wrapperStyles.push(inlineStyle);
|
|
5882
|
-
this.
|
|
6143
|
+
this.setShadowHTML(`
|
|
5883
6144
|
<style>
|
|
5884
6145
|
/* Functional styles ONLY — content presentation is owned by the HTML
|
|
5885
6146
|
itself (browser semantic defaults + the model's inline styles).
|
|
@@ -5899,9 +6160,10 @@ class CardHtml extends BaseElement {
|
|
|
5899
6160
|
</style>
|
|
5900
6161
|
<div
|
|
5901
6162
|
class="card-html ${isMobile ? 'card-mobile' : 'card-desktop'}"
|
|
6163
|
+
data-responsive-content="preserve"
|
|
5902
6164
|
style="${wrapperStyles.join(';')}"
|
|
5903
6165
|
>${safeHtml}</div>
|
|
5904
|
-
|
|
6166
|
+
`);
|
|
5905
6167
|
// Restore scroll state after replacement (only relevant with maxHeight)
|
|
5906
6168
|
if (maxHeight != null) {
|
|
5907
6169
|
const wrapper = this.shadowRoot.querySelector('.card-html');
|
|
@@ -6072,7 +6334,7 @@ class CardDatePicker extends BaseElement {
|
|
|
6072
6334
|
displayText = showTodayPrefix && isToday(date) ? `${todayText}, ${formatted}` : formatted;
|
|
6073
6335
|
}
|
|
6074
6336
|
const hostWidth = width != null && width !== '' ? `width: ${this.toCSS(width)};` : '';
|
|
6075
|
-
this.
|
|
6337
|
+
this.setShadowHTML(`
|
|
6076
6338
|
<style>
|
|
6077
6339
|
:host {
|
|
6078
6340
|
display: inline-block;
|
|
@@ -6175,7 +6437,7 @@ class CardDatePicker extends BaseElement {
|
|
|
6175
6437
|
<span class="trigger-arrow ${this._open ? 'open' : ''}">${CHEVRON_ICON}</span>
|
|
6176
6438
|
</div>
|
|
6177
6439
|
<div class="panel ${this._open ? 'open' : ''}"><div class="panel-calendar"></div></div>
|
|
6178
|
-
|
|
6440
|
+
`);
|
|
6179
6441
|
if (disabled)
|
|
6180
6442
|
return;
|
|
6181
6443
|
const trigger = this.shadowRoot.querySelector('.trigger');
|
|
@@ -6407,7 +6669,7 @@ class CardAudio extends BaseElement {
|
|
|
6407
6669
|
const valid = isSafeMediaSrc(src);
|
|
6408
6670
|
// Audible autoplay is blocked by browsers — only ever autoplay muted
|
|
6409
6671
|
const effectiveMuted = muted || autoplay;
|
|
6410
|
-
this.
|
|
6672
|
+
this.setShadowHTML(`
|
|
6411
6673
|
<style>
|
|
6412
6674
|
:host {
|
|
6413
6675
|
display: block;
|
|
@@ -6438,7 +6700,7 @@ class CardAudio extends BaseElement {
|
|
|
6438
6700
|
style="${inlineStyle}"
|
|
6439
6701
|
></audio>`
|
|
6440
6702
|
: `<div class="placeholder" style="${inlineStyle}">暂无可播放的媒体源</div>`}
|
|
6441
|
-
|
|
6703
|
+
`);
|
|
6442
6704
|
const media = this.shadowRoot.querySelector('audio');
|
|
6443
6705
|
if (media) {
|
|
6444
6706
|
// The `muted` content attribute only applies before load — set the
|
|
@@ -6492,7 +6754,7 @@ class CardVideo extends BaseElement {
|
|
|
6492
6754
|
const videoHeight = height != null && height !== '' ? `height: ${this.toCSS(height)};` : '';
|
|
6493
6755
|
const valid = isSafeMediaSrc(src);
|
|
6494
6756
|
const effectiveMuted = muted || autoplay;
|
|
6495
|
-
this.
|
|
6757
|
+
this.setShadowHTML(`
|
|
6496
6758
|
<style>
|
|
6497
6759
|
:host {
|
|
6498
6760
|
display: block;
|
|
@@ -6530,7 +6792,7 @@ class CardVideo extends BaseElement {
|
|
|
6530
6792
|
style="${inlineStyle}"
|
|
6531
6793
|
></video>`
|
|
6532
6794
|
: `<div class="placeholder" style="${inlineStyle}">暂无可播放的媒体源</div>`}
|
|
6533
|
-
|
|
6795
|
+
`);
|
|
6534
6796
|
const media = this.shadowRoot.querySelector('video');
|
|
6535
6797
|
if (media) {
|
|
6536
6798
|
media.muted = effectiveMuted;
|
|
@@ -6574,7 +6836,7 @@ class CardSwitch extends BaseElement {
|
|
|
6574
6836
|
...(thumbStyle || {}),
|
|
6575
6837
|
backgroundColor: thumbColor,
|
|
6576
6838
|
}, isExpressionResultStyle);
|
|
6577
|
-
this.
|
|
6839
|
+
this.setShadowHTML(`
|
|
6578
6840
|
<style>
|
|
6579
6841
|
:host {
|
|
6580
6842
|
display: inline-flex;
|
|
@@ -6642,7 +6904,7 @@ class CardSwitch extends BaseElement {
|
|
|
6642
6904
|
<span class="switch-thumb" part="thumb" style="${thumbInline}"></span>
|
|
6643
6905
|
</button>
|
|
6644
6906
|
</div>
|
|
6645
|
-
|
|
6907
|
+
`);
|
|
6646
6908
|
const button = this.shadowRoot.querySelector('.switch-track');
|
|
6647
6909
|
button?.addEventListener('click', () => {
|
|
6648
6910
|
if (disabled || readOnly)
|
|
@@ -6761,7 +7023,7 @@ class CardChoiceItem extends BaseElement {
|
|
|
6761
7023
|
this.setAttribute('data-selected', String(selected));
|
|
6762
7024
|
this.toggleAttribute('data-disabled', fullyDisabled);
|
|
6763
7025
|
this.toggleAttribute('data-selection-blocked', selectionBlocked);
|
|
6764
|
-
this.
|
|
7026
|
+
this.setShadowHTML(`
|
|
6765
7027
|
<style>
|
|
6766
7028
|
:host {
|
|
6767
7029
|
display: block;
|
|
@@ -6852,7 +7114,7 @@ class CardChoiceItem extends BaseElement {
|
|
|
6852
7114
|
</div>
|
|
6853
7115
|
${indicatorPosition === 'end' ? this.renderIndicator(indicatorRole, selected, selectionUnavailable, tabIndex, ariaLabel, indicatorInline, checkedMark) : ''}
|
|
6854
7116
|
</div>
|
|
6855
|
-
|
|
7117
|
+
`);
|
|
6856
7118
|
const root = this.shadowRoot.querySelector('.choice-item');
|
|
6857
7119
|
const indicator = this.shadowRoot.querySelector('.choice-indicator');
|
|
6858
7120
|
indicator?.addEventListener('click', (event) => {
|
|
@@ -6993,7 +7255,7 @@ class CardChoiceList extends BaseElement {
|
|
|
6993
7255
|
this.addEventListener('choice-request-change', this._onChoiceRequest);
|
|
6994
7256
|
this.removeEventListener('choice-navigate', this._onChoiceNavigate);
|
|
6995
7257
|
this.addEventListener('choice-navigate', this._onChoiceNavigate);
|
|
6996
|
-
this.
|
|
7258
|
+
this.setShadowHTML(`
|
|
6997
7259
|
<style>
|
|
6998
7260
|
:host {
|
|
6999
7261
|
display: block;
|
|
@@ -7016,7 +7278,7 @@ class CardChoiceList extends BaseElement {
|
|
|
7016
7278
|
>
|
|
7017
7279
|
<slot></slot>
|
|
7018
7280
|
</div>
|
|
7019
|
-
|
|
7281
|
+
`);
|
|
7020
7282
|
this.shadowRoot.querySelector('slot')?.addEventListener('slotchange', () => this.syncItems());
|
|
7021
7283
|
queueMicrotask(() => this.syncItems());
|
|
7022
7284
|
}
|
|
@@ -7227,159 +7489,159 @@ if (typeof customElements !== 'undefined'
|
|
|
7227
7489
|
* Render a Text node as an `<ai-card-text>` Custom Element.
|
|
7228
7490
|
* All text styling is scoped inside Shadow DOM.
|
|
7229
7491
|
*/
|
|
7230
|
-
function renderText(node, props, isMobile) {
|
|
7492
|
+
function renderText(node, props, isMobile, responsive) {
|
|
7231
7493
|
const el = document.createElement(CardText.is);
|
|
7232
|
-
el.setData(node, props, isMobile);
|
|
7494
|
+
el.setData(node, props, isMobile, responsive);
|
|
7233
7495
|
return el;
|
|
7234
7496
|
}
|
|
7235
7497
|
/**
|
|
7236
7498
|
* Render a Button node as an `<ai-card-button>` Custom Element.
|
|
7237
7499
|
* Supports variants (primary/secondary/text/danger), sizes, disabled state.
|
|
7238
7500
|
*/
|
|
7239
|
-
function renderButton(node, props, isMobile) {
|
|
7501
|
+
function renderButton(node, props, isMobile, responsive) {
|
|
7240
7502
|
const el = document.createElement(CardButton.is);
|
|
7241
|
-
el.setData(node, props, isMobile);
|
|
7503
|
+
el.setData(node, props, isMobile, responsive);
|
|
7242
7504
|
return el;
|
|
7243
7505
|
}
|
|
7244
7506
|
/**
|
|
7245
7507
|
* Render an Input node as an `<ai-card-input>` Custom Element.
|
|
7246
7508
|
* Supports text/password/number/textarea, label, placeholder, validation.
|
|
7247
7509
|
*/
|
|
7248
|
-
function renderInput(node, props, isMobile) {
|
|
7510
|
+
function renderInput(node, props, isMobile, responsive) {
|
|
7249
7511
|
const el = document.createElement(CardInput.is);
|
|
7250
|
-
el.setData(node, props, isMobile);
|
|
7512
|
+
el.setData(node, props, isMobile, responsive);
|
|
7251
7513
|
return el;
|
|
7252
7514
|
}
|
|
7253
7515
|
/**
|
|
7254
7516
|
* Render an Image node as an `<ai-card-image>` Custom Element.
|
|
7255
7517
|
* Supports click-to-zoom lightbox preview.
|
|
7256
7518
|
*/
|
|
7257
|
-
function renderImage(node, props, isMobile) {
|
|
7519
|
+
function renderImage(node, props, isMobile, responsive) {
|
|
7258
7520
|
const el = document.createElement(CardImage.is);
|
|
7259
|
-
el.setData(node, props, isMobile);
|
|
7521
|
+
el.setData(node, props, isMobile, responsive);
|
|
7260
7522
|
return el;
|
|
7261
7523
|
}
|
|
7262
7524
|
/** Render a Divider node. */
|
|
7263
|
-
function renderDivider(node, props, isMobile) {
|
|
7525
|
+
function renderDivider(node, props, isMobile, responsive) {
|
|
7264
7526
|
const el = document.createElement(CardDivider.is);
|
|
7265
|
-
el.setData(node, props, isMobile);
|
|
7527
|
+
el.setData(node, props, isMobile, responsive);
|
|
7266
7528
|
return el;
|
|
7267
7529
|
}
|
|
7268
7530
|
/** Render a Rate node. */
|
|
7269
|
-
function renderRate(node, props, isMobile) {
|
|
7531
|
+
function renderRate(node, props, isMobile, responsive) {
|
|
7270
7532
|
const el = document.createElement(CardRate.is);
|
|
7271
|
-
el.setData(node, props, isMobile);
|
|
7533
|
+
el.setData(node, props, isMobile, responsive);
|
|
7272
7534
|
return el;
|
|
7273
7535
|
}
|
|
7274
7536
|
/** Render a Counter node. */
|
|
7275
|
-
function renderCounter(node, props, isMobile) {
|
|
7537
|
+
function renderCounter(node, props, isMobile, responsive) {
|
|
7276
7538
|
const el = document.createElement(CardCounter.is);
|
|
7277
|
-
el.setData(node, props, isMobile);
|
|
7539
|
+
el.setData(node, props, isMobile, responsive);
|
|
7278
7540
|
return el;
|
|
7279
7541
|
}
|
|
7280
7542
|
/** Render a Tag node. */
|
|
7281
|
-
function renderTag(node, props, isMobile) {
|
|
7543
|
+
function renderTag(node, props, isMobile, responsive) {
|
|
7282
7544
|
const el = document.createElement(CardTag.is);
|
|
7283
|
-
el.setData(node, props, isMobile);
|
|
7545
|
+
el.setData(node, props, isMobile, responsive);
|
|
7284
7546
|
return el;
|
|
7285
7547
|
}
|
|
7286
7548
|
/** Render a Select node. */
|
|
7287
|
-
function renderSelect(node, props, isMobile) {
|
|
7549
|
+
function renderSelect(node, props, isMobile, responsive) {
|
|
7288
7550
|
const el = document.createElement(CardSelect.is);
|
|
7289
|
-
el.setData(node, props, isMobile);
|
|
7551
|
+
el.setData(node, props, isMobile, responsive);
|
|
7290
7552
|
return el;
|
|
7291
7553
|
}
|
|
7292
7554
|
/** Render a PasscodeInput node. */
|
|
7293
|
-
function renderPasscodeInput(node, props, isMobile) {
|
|
7555
|
+
function renderPasscodeInput(node, props, isMobile, responsive) {
|
|
7294
7556
|
const el = document.createElement(CardPasscodeInput.is);
|
|
7295
|
-
el.setData(node, props, isMobile);
|
|
7557
|
+
el.setData(node, props, isMobile, responsive);
|
|
7296
7558
|
return el;
|
|
7297
7559
|
}
|
|
7298
7560
|
/** Render an Icon node. */
|
|
7299
|
-
function renderIcon(node, props, isMobile) {
|
|
7561
|
+
function renderIcon(node, props, isMobile, responsive) {
|
|
7300
7562
|
const el = document.createElement(CardIcon.is);
|
|
7301
|
-
el.setData(node, props, isMobile);
|
|
7563
|
+
el.setData(node, props, isMobile, responsive);
|
|
7302
7564
|
return el;
|
|
7303
7565
|
}
|
|
7304
7566
|
/** Render a Form node. */
|
|
7305
|
-
function renderForm(node, props, isMobile) {
|
|
7567
|
+
function renderForm(node, props, isMobile, responsive) {
|
|
7306
7568
|
const el = document.createElement(CardForm.is);
|
|
7307
|
-
el.setData(node, props, isMobile);
|
|
7569
|
+
el.setData(node, props, isMobile, responsive);
|
|
7308
7570
|
return el;
|
|
7309
7571
|
}
|
|
7310
7572
|
/** Render a Loading node. */
|
|
7311
|
-
function renderLoading(node, props, isMobile) {
|
|
7573
|
+
function renderLoading(node, props, isMobile, responsive) {
|
|
7312
7574
|
const el = document.createElement(CardLoading.is);
|
|
7313
|
-
el.setData(node, props, isMobile);
|
|
7575
|
+
el.setData(node, props, isMobile, responsive);
|
|
7314
7576
|
return el;
|
|
7315
7577
|
}
|
|
7316
7578
|
/** Render a Progress node. */
|
|
7317
|
-
function renderProgress(node, props, isMobile) {
|
|
7579
|
+
function renderProgress(node, props, isMobile, responsive) {
|
|
7318
7580
|
const el = document.createElement(CardProgress.is);
|
|
7319
|
-
el.setData(node, props, isMobile);
|
|
7581
|
+
el.setData(node, props, isMobile, responsive);
|
|
7320
7582
|
return el;
|
|
7321
7583
|
}
|
|
7322
7584
|
/** Render a Steps node. */
|
|
7323
|
-
function renderSteps(node, props, isMobile) {
|
|
7585
|
+
function renderSteps(node, props, isMobile, responsive) {
|
|
7324
7586
|
const el = document.createElement(CardSteps.is);
|
|
7325
|
-
el.setData(node, props, isMobile);
|
|
7587
|
+
el.setData(node, props, isMobile, responsive);
|
|
7326
7588
|
return el;
|
|
7327
7589
|
}
|
|
7328
7590
|
/**
|
|
7329
7591
|
* Render a Collapse node as an `<ai-card-collapse>` Custom Element.
|
|
7330
7592
|
* Expandable/collapsible panel for AI thinking content.
|
|
7331
7593
|
*/
|
|
7332
|
-
function renderCollapse(node, props, isMobile) {
|
|
7594
|
+
function renderCollapse(node, props, isMobile, responsive) {
|
|
7333
7595
|
const el = document.createElement(CardCollapse.is);
|
|
7334
|
-
el.setData(node, props, isMobile);
|
|
7596
|
+
el.setData(node, props, isMobile, responsive);
|
|
7335
7597
|
return el;
|
|
7336
7598
|
}
|
|
7337
7599
|
/**
|
|
7338
7600
|
* Render an Html node as an `<ai-card-html>` Custom Element.
|
|
7339
7601
|
* Model-emitted HTML fragments, allow-list sanitized before display.
|
|
7340
7602
|
*/
|
|
7341
|
-
function renderHtml(node, props, isMobile) {
|
|
7603
|
+
function renderHtml(node, props, isMobile, responsive) {
|
|
7342
7604
|
const el = document.createElement(CardHtml.is);
|
|
7343
|
-
el.setData(node, props, isMobile);
|
|
7605
|
+
el.setData(node, props, isMobile, responsive);
|
|
7344
7606
|
return el;
|
|
7345
7607
|
}
|
|
7346
7608
|
/**
|
|
7347
7609
|
* Render a DatePicker node as an `<ai-card-date-picker>` Custom Element.
|
|
7348
7610
|
* Trigger + popup calendar panel (vanilla-calendar-pro).
|
|
7349
7611
|
*/
|
|
7350
|
-
function renderDatePicker(node, props, isMobile) {
|
|
7612
|
+
function renderDatePicker(node, props, isMobile, responsive) {
|
|
7351
7613
|
const el = document.createElement(CardDatePicker.is);
|
|
7352
|
-
el.setData(node, props, isMobile);
|
|
7614
|
+
el.setData(node, props, isMobile, responsive);
|
|
7353
7615
|
return el;
|
|
7354
7616
|
}
|
|
7355
7617
|
/** Render an Audio node as an `<ai-card-audio>` Custom Element. */
|
|
7356
|
-
function renderAudio(node, props, isMobile) {
|
|
7618
|
+
function renderAudio(node, props, isMobile, responsive) {
|
|
7357
7619
|
const el = document.createElement(CardAudio.is);
|
|
7358
|
-
el.setData(node, props, isMobile);
|
|
7620
|
+
el.setData(node, props, isMobile, responsive);
|
|
7359
7621
|
return el;
|
|
7360
7622
|
}
|
|
7361
7623
|
/** Render a Video node as an `<ai-card-video>` Custom Element. */
|
|
7362
|
-
function renderVideo(node, props, isMobile) {
|
|
7624
|
+
function renderVideo(node, props, isMobile, responsive) {
|
|
7363
7625
|
const el = document.createElement(CardVideo.is);
|
|
7364
|
-
el.setData(node, props, isMobile);
|
|
7626
|
+
el.setData(node, props, isMobile, responsive);
|
|
7365
7627
|
return el;
|
|
7366
7628
|
}
|
|
7367
7629
|
/** Render a Switch node. */
|
|
7368
|
-
function renderSwitch(node, props, isMobile) {
|
|
7630
|
+
function renderSwitch(node, props, isMobile, responsive) {
|
|
7369
7631
|
const el = document.createElement(CardSwitch.is);
|
|
7370
|
-
el.setData(node, props, isMobile);
|
|
7632
|
+
el.setData(node, props, isMobile, responsive);
|
|
7371
7633
|
return el;
|
|
7372
7634
|
}
|
|
7373
7635
|
/** Render a ChoiceList node. */
|
|
7374
|
-
function renderChoiceList(node, props, isMobile) {
|
|
7636
|
+
function renderChoiceList(node, props, isMobile, responsive) {
|
|
7375
7637
|
const el = document.createElement(CardChoiceList.is);
|
|
7376
|
-
el.setData(node, props, isMobile);
|
|
7638
|
+
el.setData(node, props, isMobile, responsive);
|
|
7377
7639
|
return el;
|
|
7378
7640
|
}
|
|
7379
7641
|
/** Render a ChoiceItem node. */
|
|
7380
|
-
function renderChoiceItem(node, props, isMobile) {
|
|
7642
|
+
function renderChoiceItem(node, props, isMobile, responsive) {
|
|
7381
7643
|
const el = document.createElement(CardChoiceItem.is);
|
|
7382
|
-
el.setData(node, props, isMobile);
|
|
7644
|
+
el.setData(node, props, isMobile, responsive);
|
|
7383
7645
|
return el;
|
|
7384
7646
|
}
|
|
7385
7647
|
/**
|
|
@@ -7389,7 +7651,7 @@ function renderChoiceItem(node, props, isMobile) {
|
|
|
7389
7651
|
*
|
|
7390
7652
|
* Layout is handled by the slot system in slots.ts (called from renderCard).
|
|
7391
7653
|
*/
|
|
7392
|
-
function renderDefault(node, props, isMobile) {
|
|
7654
|
+
function renderDefault(node, props, isMobile, responsive) {
|
|
7393
7655
|
const div = document.createElement('div');
|
|
7394
7656
|
div.className = `card-element card-${node.type.toLowerCase()} ${isMobile ? 'card-mobile' : 'card-desktop'}`;
|
|
7395
7657
|
div.setAttribute('data-card-id', node.id);
|
|
@@ -7401,6 +7663,7 @@ function renderDefault(node, props, isMobile) {
|
|
|
7401
7663
|
if (resolvedStyle)
|
|
7402
7664
|
div.style.cssText += ';' + resolvedStyle;
|
|
7403
7665
|
}
|
|
7666
|
+
applyResponsiveStyles(div, createResponsiveContext(isMobile, responsive));
|
|
7404
7667
|
return div;
|
|
7405
7668
|
}
|
|
7406
7669
|
// ─── Registry ────────────────────────────────────────────────────
|
|
@@ -7475,18 +7738,9 @@ function renderBoundCard(container, schema, options) {
|
|
|
7475
7738
|
let currentMaterialized;
|
|
7476
7739
|
let revision = 0;
|
|
7477
7740
|
let disposed = false;
|
|
7478
|
-
|
|
7741
|
+
const isMobile = options.isMobile === true;
|
|
7479
7742
|
let actionQueue = Promise.resolve();
|
|
7480
7743
|
let lifecycleQueue = Promise.resolve();
|
|
7481
|
-
const removeViewportListener = options.isMobile == null
|
|
7482
|
-
? onViewportChange((mobile) => {
|
|
7483
|
-
if (disposed)
|
|
7484
|
-
return;
|
|
7485
|
-
isMobile = mobile;
|
|
7486
|
-
const candidate = prepareCandidate(variables);
|
|
7487
|
-
publishDOM(candidate, false);
|
|
7488
|
-
})
|
|
7489
|
-
: () => { };
|
|
7490
7744
|
function expressionContextFor(node) {
|
|
7491
7745
|
return isBoundRenderTreeNode(node)
|
|
7492
7746
|
? createExpressionContext(node.scope)
|
|
@@ -7545,7 +7799,7 @@ function renderBoundCard(container, schema, options) {
|
|
|
7545
7799
|
|| resolved === 1);
|
|
7546
7800
|
}
|
|
7547
7801
|
const renderer = (componentRenderers[node.type] ?? componentRenderers._default);
|
|
7548
|
-
const el = renderer(node, resolvedProps, isMobile);
|
|
7802
|
+
const el = renderer(node, resolvedProps, isMobile, options.responsive);
|
|
7549
7803
|
if (isDisabled) {
|
|
7550
7804
|
el.setAttribute('data-disabled', 'true');
|
|
7551
7805
|
el.style.background = '#F5F5F5';
|
|
@@ -7608,6 +7862,7 @@ function renderBoundCard(container, schema, options) {
|
|
|
7608
7862
|
htmlChild.style.setProperty('--card-disabled-color', '#C0C0C0');
|
|
7609
7863
|
});
|
|
7610
7864
|
}
|
|
7865
|
+
applyResponsiveStyles(el, createResponsiveContext(isMobile, options.responsive));
|
|
7611
7866
|
return el;
|
|
7612
7867
|
}
|
|
7613
7868
|
function prepareCandidate(draft) {
|
|
@@ -7636,9 +7891,13 @@ function renderBoundCard(container, schema, options) {
|
|
|
7636
7891
|
return selected;
|
|
7637
7892
|
}
|
|
7638
7893
|
function createLifecycleActionContext(node) {
|
|
7639
|
-
const writeLiveVariable = (key, value) => {
|
|
7894
|
+
const writeLiveVariable = (key, value, silent = false) => {
|
|
7640
7895
|
if (disposed)
|
|
7641
7896
|
return;
|
|
7897
|
+
if (silent) {
|
|
7898
|
+
writeDraftVariable(variables, key, cloneJsonData(value));
|
|
7899
|
+
return;
|
|
7900
|
+
}
|
|
7642
7901
|
updateVariables({ [key]: value });
|
|
7643
7902
|
};
|
|
7644
7903
|
return {
|
|
@@ -7652,7 +7911,9 @@ function renderBoundCard(container, schema, options) {
|
|
|
7652
7911
|
parameterResolver: node.bindingDialect === 'a2ui'
|
|
7653
7912
|
? createA2UIParameterResolver(variables, node.dataPath)
|
|
7654
7913
|
: undefined,
|
|
7655
|
-
variableWriter: (key, value) =>
|
|
7914
|
+
variableWriter: (key, value, options) => {
|
|
7915
|
+
writeLiveVariable(key, value, options.silent);
|
|
7916
|
+
},
|
|
7656
7917
|
botId: options.botId,
|
|
7657
7918
|
inflightRequests,
|
|
7658
7919
|
};
|
|
@@ -7796,7 +8057,6 @@ function renderBoundCard(container, schema, options) {
|
|
|
7796
8057
|
return;
|
|
7797
8058
|
disposed = true;
|
|
7798
8059
|
abortController.abort();
|
|
7799
|
-
removeViewportListener();
|
|
7800
8060
|
disposeChartsIn(container);
|
|
7801
8061
|
container.replaceChildren();
|
|
7802
8062
|
const lifecycleNodes = [...activeLifecycleNodes.entries()];
|
|
@@ -7947,13 +8207,7 @@ function renderStaticCard(container, schema, options) {
|
|
|
7947
8207
|
}
|
|
7948
8208
|
let actionContext = buildActionContext();
|
|
7949
8209
|
// 5. Responsive
|
|
7950
|
-
|
|
7951
|
-
const removeViewportListener = options.isMobile == null
|
|
7952
|
-
? onViewportChange((mobile) => {
|
|
7953
|
-
isMobile = mobile;
|
|
7954
|
-
rerender();
|
|
7955
|
-
})
|
|
7956
|
-
: () => { };
|
|
8210
|
+
const isMobile = options.isMobile === true;
|
|
7957
8211
|
// 6. Schema-level action definitions (for string references in events)
|
|
7958
8212
|
const schemaActions = schema.actions ?? {};
|
|
7959
8213
|
// 7. Render function
|
|
@@ -7962,7 +8216,7 @@ function renderStaticCard(container, schema, options) {
|
|
|
7962
8216
|
const mediaStates = captureMediaStates(container);
|
|
7963
8217
|
disposeChartsIn(container); // tear down old chart instances before clearing
|
|
7964
8218
|
container.innerHTML = '';
|
|
7965
|
-
const dom = renderNode(tree, variables, actionContext, isMobile, lifecycleManager, schemaActions);
|
|
8219
|
+
const dom = renderNode(tree, variables, actionContext, isMobile, options.responsive, lifecycleManager, schemaActions);
|
|
7966
8220
|
container.appendChild(dom);
|
|
7967
8221
|
restoreScrollPositions(container, scrollPositions);
|
|
7968
8222
|
restoreMediaStates(container, mediaStates);
|
|
@@ -7977,7 +8231,6 @@ function renderStaticCard(container, schema, options) {
|
|
|
7977
8231
|
return {
|
|
7978
8232
|
dispose() {
|
|
7979
8233
|
abortController.abort();
|
|
7980
|
-
removeViewportListener();
|
|
7981
8234
|
lifecycleManager.dispose(actionContext);
|
|
7982
8235
|
disposeChartsIn(container);
|
|
7983
8236
|
container.innerHTML = '';
|
|
@@ -8060,7 +8313,7 @@ function restoreMediaStates(root, states) {
|
|
|
8060
8313
|
});
|
|
8061
8314
|
}
|
|
8062
8315
|
// ─── Recursive Node Renderer ─────────────────────────────────────
|
|
8063
|
-
function renderNode(node, variables, actionContext, isMobile, lifecycleManager, schemaActions) {
|
|
8316
|
+
function renderNode(node, variables, actionContext, isMobile, responsive, lifecycleManager, schemaActions) {
|
|
8064
8317
|
// Check directives.visible
|
|
8065
8318
|
if (node.directives?.visible) {
|
|
8066
8319
|
const visibleExpr = node.directives.visible;
|
|
@@ -8092,7 +8345,7 @@ function renderNode(node, variables, actionContext, isMobile, lifecycleManager,
|
|
|
8092
8345
|
}
|
|
8093
8346
|
// Lookup component renderer
|
|
8094
8347
|
const renderer = componentRenderers[node.type] ?? componentRenderers['_default'];
|
|
8095
|
-
const el = renderer(node, resolvedProps, isMobile);
|
|
8348
|
+
const el = renderer(node, resolvedProps, isMobile, responsive);
|
|
8096
8349
|
// Apply disabled styling & attribute
|
|
8097
8350
|
if (isDisabled) {
|
|
8098
8351
|
el.setAttribute('data-disabled', 'true');
|
|
@@ -8154,7 +8407,7 @@ function renderNode(node, variables, actionContext, isMobile, lifecycleManager,
|
|
|
8154
8407
|
lifecycleManager.mount(node.id, actionContext);
|
|
8155
8408
|
}
|
|
8156
8409
|
// Render children — use slot layout if applicable, otherwise flat append
|
|
8157
|
-
const renderChild = (child) => renderNode(child, variables, actionContext, isMobile, lifecycleManager, schemaActions);
|
|
8410
|
+
const renderChild = (child) => renderNode(child, variables, actionContext, isMobile, responsive, lifecycleManager, schemaActions);
|
|
8158
8411
|
// Build children-by-id map for layouts that reference IDs (columns groups, float overlays)
|
|
8159
8412
|
const childrenMap = {};
|
|
8160
8413
|
for (const child of node.children) {
|
|
@@ -8175,6 +8428,7 @@ function renderNode(node, variables, actionContext, isMobile, lifecycleManager,
|
|
|
8175
8428
|
htmlChild.style.setProperty('--card-disabled-color', '#C0C0C0');
|
|
8176
8429
|
});
|
|
8177
8430
|
}
|
|
8431
|
+
applyResponsiveStyles(el, createResponsiveContext(isMobile, responsive));
|
|
8178
8432
|
return el;
|
|
8179
8433
|
}
|
|
8180
8434
|
/** Map schema event names → DOM event names */
|
|
@@ -8249,7 +8503,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8249
8503
|
let partialVariablesSent = false;
|
|
8250
8504
|
let partialFinalized = false;
|
|
8251
8505
|
let variables = { ...options.variables };
|
|
8252
|
-
|
|
8506
|
+
const isMobile = options.isMobile === true;
|
|
8253
8507
|
let currentSchema = null;
|
|
8254
8508
|
let currentSurfaceId = null;
|
|
8255
8509
|
let currentMaterialized = null;
|
|
@@ -8261,16 +8515,6 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8261
8515
|
const activeBoundLifecycles = new Map();
|
|
8262
8516
|
const mountedBoundLifecycles = new Map();
|
|
8263
8517
|
const boundLifecycleGenerations = new Map();
|
|
8264
|
-
// Responsive viewport detection
|
|
8265
|
-
const removeViewportListener = options.isMobile == null
|
|
8266
|
-
? onViewportChange((mobile) => {
|
|
8267
|
-
isMobile = mobile;
|
|
8268
|
-
// Re-render all elements with new mobile state if schema exists
|
|
8269
|
-
if (currentSchema) {
|
|
8270
|
-
rerenderAll();
|
|
8271
|
-
}
|
|
8272
|
-
})
|
|
8273
|
-
: () => { };
|
|
8274
8518
|
// ─── Action Context ─────────────────────────────────────────────
|
|
8275
8519
|
function buildActionContext() {
|
|
8276
8520
|
return {
|
|
@@ -8410,7 +8654,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8410
8654
|
indexes.props.set(node.id, boundNodeFingerprint(node, renderVariables, resolvedProps));
|
|
8411
8655
|
const isDisabled = computeBoundDisabled(node, renderVariables);
|
|
8412
8656
|
const renderer = (componentRenderers[node.type] ?? componentRenderers._default);
|
|
8413
|
-
const element = renderer(node, resolvedProps, isMobile);
|
|
8657
|
+
const element = renderer(node, resolvedProps, isMobile, options.responsive);
|
|
8414
8658
|
if (isDisabled) {
|
|
8415
8659
|
element.setAttribute('data-disabled', 'true');
|
|
8416
8660
|
element.style.background = '#F5F5F5';
|
|
@@ -8461,6 +8705,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8461
8705
|
element.appendChild(renderChild(child));
|
|
8462
8706
|
}
|
|
8463
8707
|
}
|
|
8708
|
+
applyResponsiveStyles(element, createResponsiveContext(isMobile, options.responsive));
|
|
8464
8709
|
return element;
|
|
8465
8710
|
}
|
|
8466
8711
|
/**
|
|
@@ -8502,7 +8747,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8502
8747
|
}
|
|
8503
8748
|
// Create element
|
|
8504
8749
|
const renderer = componentRenderers[node.type] ?? componentRenderers['_default'];
|
|
8505
|
-
const el = renderer(node, resolvedProps, isMobile);
|
|
8750
|
+
const el = renderer(node, resolvedProps, isMobile, options.responsive);
|
|
8506
8751
|
// Apply disabled
|
|
8507
8752
|
if (isDisabled) {
|
|
8508
8753
|
el.setAttribute('data-disabled', 'true');
|
|
@@ -8559,6 +8804,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8559
8804
|
el.appendChild(renderChild(child));
|
|
8560
8805
|
}
|
|
8561
8806
|
}
|
|
8807
|
+
applyResponsiveStyles(el, createResponsiveContext(isMobile, options.responsive));
|
|
8562
8808
|
return el;
|
|
8563
8809
|
}
|
|
8564
8810
|
// ─── Incremental Rendering Helpers ──────────────────────────────
|
|
@@ -8689,7 +8935,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8689
8935
|
if (propsCache.get(id) === key)
|
|
8690
8936
|
continue;
|
|
8691
8937
|
if ('updateProps' in el) {
|
|
8692
|
-
el.updateProps(resolved, isMobile);
|
|
8938
|
+
el.updateProps(resolved, isMobile, options.responsive);
|
|
8693
8939
|
propsCache.set(id, key);
|
|
8694
8940
|
}
|
|
8695
8941
|
else if (!replaceSubtree(schema, id, el)) {
|
|
@@ -9640,17 +9886,24 @@ function renderStreamingCard(container, options = {}) {
|
|
|
9640
9886
|
console.warn('[renderStreamingCard] Deferred render (incomplete schema):', error);
|
|
9641
9887
|
}
|
|
9642
9888
|
}
|
|
9643
|
-
/**
|
|
9644
|
-
* Re-render all elements (viewport changes — isMobile affects every renderer's
|
|
9645
|
-
* output, so the incremental diff cache cannot be reused here).
|
|
9646
|
-
*/
|
|
9647
|
-
function rerenderAll() {
|
|
9648
|
-
actionContext = buildActionContext();
|
|
9649
|
-
safeRenderFull();
|
|
9650
|
-
}
|
|
9651
9889
|
// ─── Engine Event Handlers ──────────────────────────────────────
|
|
9652
9890
|
const engine = new StreamingEngine({
|
|
9653
9891
|
onSurfaceCreated(surfaceId, schemaInput) {
|
|
9892
|
+
if (schemaInput) {
|
|
9893
|
+
const nextSchema = engine.getSchema(surfaceId)
|
|
9894
|
+
?? normalizeSchema(schemaInput);
|
|
9895
|
+
if (requiresBindingMaterialization(nextSchema)) {
|
|
9896
|
+
const baseRevision = boundRevision;
|
|
9897
|
+
const draft = cloneJsonData({
|
|
9898
|
+
...nextSchema.variables,
|
|
9899
|
+
...options.variables,
|
|
9900
|
+
});
|
|
9901
|
+
const prepared = prepareBoundFull(nextSchema, cloneJsonData(draft));
|
|
9902
|
+
commitBoundFull(prepared, nextSchema, draft, baseRevision, () => replaceRootContents(nextSchema.variables, draft));
|
|
9903
|
+
currentSurfaceId = surfaceId;
|
|
9904
|
+
return;
|
|
9905
|
+
}
|
|
9906
|
+
}
|
|
9654
9907
|
teardownBoundLifecycles();
|
|
9655
9908
|
boundRevision += 1;
|
|
9656
9909
|
currentSurfaceId = surfaceId;
|
|
@@ -9840,7 +10093,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
9840
10093
|
continue;
|
|
9841
10094
|
const typeChanged = el.getAttribute('data-card-type') !== element.type;
|
|
9842
10095
|
if (!typeChanged && 'updateProps' in el) {
|
|
9843
|
-
el.updateProps(resolved, isMobile);
|
|
10096
|
+
el.updateProps(resolved, isMobile, options.responsive);
|
|
9844
10097
|
propsCache.set(id, key);
|
|
9845
10098
|
}
|
|
9846
10099
|
else if (!typeChanged && isChildrenOnlyChange(propsCache.get(id), key, element)) {
|
|
@@ -9950,7 +10203,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
9950
10203
|
const node = indexBoundNodes(nextMaterialized.root).get(elementId);
|
|
9951
10204
|
if (node && node.id === node.sourceId && 'updateProps' in el) {
|
|
9952
10205
|
const resolved = resolveBoundNodeProps(node, variables);
|
|
9953
|
-
el.updateProps(resolved, isMobile);
|
|
10206
|
+
el.updateProps(resolved, isMobile, options.responsive);
|
|
9954
10207
|
propsCache.set(elementId, boundNodeFingerprint(node, variables, resolved));
|
|
9955
10208
|
currentMaterialized = nextMaterialized;
|
|
9956
10209
|
boundRevision += 1;
|
|
@@ -9977,7 +10230,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
9977
10230
|
}
|
|
9978
10231
|
}
|
|
9979
10232
|
},
|
|
9980
|
-
onSurfaceDeleted(
|
|
10233
|
+
onSurfaceDeleted(_surfaceId) {
|
|
9981
10234
|
disposeChartsIn(container); // release old ECharts instances before clearing
|
|
9982
10235
|
container.innerHTML = '';
|
|
9983
10236
|
elementMap.clear();
|
|
@@ -10072,7 +10325,6 @@ function renderStreamingCard(container, options = {}) {
|
|
|
10072
10325
|
disposed = true;
|
|
10073
10326
|
boundRevision += 1;
|
|
10074
10327
|
abortController.abort();
|
|
10075
|
-
removeViewportListener();
|
|
10076
10328
|
teardownBoundLifecycles();
|
|
10077
10329
|
lifecycleManager.dispose(actionContext);
|
|
10078
10330
|
disposeChartsIn(container); // release ECharts instances before clearing
|
|
@@ -10241,7 +10493,7 @@ class BotSDK {
|
|
|
10241
10493
|
this._instances = [];
|
|
10242
10494
|
/** Declarative action chains loaded from actionProvider */
|
|
10243
10495
|
this._actionChains = new Map();
|
|
10244
|
-
this.botId = options.botId;
|
|
10496
|
+
this.botId = options.botId ?? '';
|
|
10245
10497
|
this.baseUrl = options.baseUrl ?? '';
|
|
10246
10498
|
// Source B: Batch-register action handler functions (sync, immediate)
|
|
10247
10499
|
if (options.onAction) {
|
|
@@ -10422,4 +10674,4 @@ class RemoteActionConfigProvider {
|
|
|
10422
10674
|
}
|
|
10423
10675
|
}
|
|
10424
10676
|
|
|
10425
|
-
export { BaseElement, BotSDK, CardButton, CardChoiceItem, CardChoiceList, CardCollapse, CardCounter, CardDivider, CardForm, CardHtml, CardIcon, CardImage, CardInput, CardLoading, CardPasscodeInput, CardProgress, CardRate, CardSelect, CardSteps, CardSwitch, CardTag, CardText, LocalActionConfigProvider, RemoteActionConfigProvider, buildStyleString, componentRenderers, connectSSE, connectStreaming, createWebActionContext, isMobileViewport, onViewportChange, pxToRem, pxToVw, registerComponent, renderCard, renderStreamingCard, resolveSize, sanitizeHtml, trimIncompleteTag };
|
|
10677
|
+
export { BaseElement, BotSDK, CardButton, CardChoiceItem, CardChoiceList, CardCollapse, CardCounter, CardDivider, CardForm, CardHtml, CardIcon, CardImage, CardInput, CardLoading, CardPasscodeInput, CardProgress, CardRate, CardSelect, CardSteps, CardSwitch, CardTag, CardText, DEFAULT_MOBILE_RESPONSIVE, LocalActionConfigProvider, RemoteActionConfigProvider, buildStyleString, componentRenderers, connectSSE, connectStreaming, convertPixelTokens, createResponsiveContext, createWebActionContext, isMobileViewport, onViewportChange, pxToRem, pxToVw, registerComponent, renderCard, renderStreamingCard, resolveSize, sanitizeHtml, trimIncompleteTag };
|