@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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getBuiltinIcon, cloneJsonData, createLifecycleManager, materializeCard, resolveActionRef, createA2UIParameterResolver, createExpressionContext, resolveA2UIDeep, hasExpression, resolveExpression, resolveDeep, resolveExpressionValue, replaceRootContents, isBoundRenderTreeNode, runActionSteps, normalizeSchema, validateSchema, requiresBindingMaterialization, parseSchema, StreamingParser, StreamingEngine, findAffectedRepeatOwners, bindingTopologyFingerprint, findTemplateRepeatOwners, extractPartialSchema, runActionStep, registry } from '@antglobal/copilot-cards-core';
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 viewport detection
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 resolveSize(value);
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 : resolveSize(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] = resolveSize(value);
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
- this._node = node;
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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 size = Number(input.size) || 24;
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
- `width="${size}" height="${size}" alt="${escapeAttr$1(name ?? 'icon')}" />`;
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)}" width="${size}" ` +
2330
- `height="${size}" color="${escapeAttr$1(String(input.color ?? 'currentColor'))}" ` +
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}px; line-height:1;">` +
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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">&times;</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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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: sizePreset.arrowSize,
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.shadowRoot.innerHTML = `
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: ${sizePreset.arrowSize}px;
3801
- height: ${sizePreset.arrowSize}px;
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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, options = [], length = 6, } = field;
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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
- : `<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
5581
- <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
5582
- </svg>`;
5583
- this.shadowRoot.innerHTML = `
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}">${bodyHTML ? bodyHTML : ''}</div>
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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.shadowRoot.innerHTML = `
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
- let isMobile = options.isMobile ?? isMobileViewport();
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) => writeLiveVariable(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
- let isMobile = options.isMobile ?? isMobileViewport();
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
- let isMobile = options.isMobile ?? isMobileViewport();
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(surfaceId) {
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 };