@antglobal/copilot-cards-web 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +36 -32
  2. package/dist/index.js +405 -142
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -96,6 +96,206 @@ function buildStyleString(styles) {
96
96
  .join(';');
97
97
  }
98
98
 
99
+ const DEFAULT_MOBILE_RESPONSIVE = Object.freeze({
100
+ unit: 'rem',
101
+ rootValue: 100,
102
+ });
103
+ function formatRem(value) {
104
+ return Number(value.toFixed(6)).toString();
105
+ }
106
+ function isIdentifierChar(value) {
107
+ if (value == null)
108
+ return false;
109
+ const codePoint = value.codePointAt(0) ?? 0;
110
+ return /[a-zA-Z0-9_-]/.test(value) || value === '\\' || codePoint >= 0x80;
111
+ }
112
+ function quotedEnd(value, start) {
113
+ const quote = value[start];
114
+ let cursor = start + 1;
115
+ while (cursor < value.length) {
116
+ if (value[cursor] === '\\') {
117
+ cursor += 2;
118
+ continue;
119
+ }
120
+ if (value[cursor] === quote)
121
+ return cursor + 1;
122
+ cursor++;
123
+ }
124
+ return value.length;
125
+ }
126
+ function identifierEscape(value, start) {
127
+ if (value[start] !== '\\' || start + 1 >= value.length)
128
+ return undefined;
129
+ let cursor = start + 1;
130
+ if (/[\n\r\f]/.test(value[cursor]))
131
+ return undefined;
132
+ let hex = '';
133
+ while (cursor < value.length && hex.length < 6 && /[0-9a-f]/i.test(value[cursor])) {
134
+ hex += value[cursor];
135
+ cursor++;
136
+ }
137
+ if (hex) {
138
+ const codePoint = Number.parseInt(hex, 16);
139
+ if (/\s/.test(value[cursor] ?? ''))
140
+ cursor++;
141
+ return {
142
+ decoded: codePoint === 0 || codePoint > 0x10ffff
143
+ ? '\uFFFD'
144
+ : String.fromCodePoint(codePoint),
145
+ end: cursor,
146
+ };
147
+ }
148
+ return { decoded: value[cursor], end: cursor + 1 };
149
+ }
150
+ function urlEnd(value, start) {
151
+ if (value[start]?.toLowerCase() !== 'u' && value[start] !== '\\') {
152
+ return undefined;
153
+ }
154
+ if (isIdentifierChar(value[start - 1]))
155
+ return undefined;
156
+ let cursor = start;
157
+ let name = '';
158
+ while (cursor < value.length) {
159
+ const char = value[cursor];
160
+ if (/[a-zA-Z0-9_-]/.test(char) || (char.codePointAt(0) ?? 0) >= 0x80) {
161
+ name += char;
162
+ cursor++;
163
+ continue;
164
+ }
165
+ const escaped = identifierEscape(value, cursor);
166
+ if (!escaped)
167
+ break;
168
+ name += escaped.decoded;
169
+ cursor = escaped.end;
170
+ }
171
+ if (name.toLowerCase() !== 'url')
172
+ return undefined;
173
+ while (/\s/.test(value[cursor] ?? ''))
174
+ cursor++;
175
+ if (value[cursor] !== '(')
176
+ return undefined;
177
+ cursor++;
178
+ let depth = 1;
179
+ while (cursor < value.length && depth > 0) {
180
+ const char = value[cursor];
181
+ if (char === '"' || char === "'") {
182
+ cursor = quotedEnd(value, cursor);
183
+ continue;
184
+ }
185
+ if (char === '\\') {
186
+ cursor += 2;
187
+ continue;
188
+ }
189
+ if (char === '(')
190
+ depth++;
191
+ if (char === ')')
192
+ depth--;
193
+ cursor++;
194
+ }
195
+ return cursor;
196
+ }
197
+ /** Convert CSS px lengths while preserving strings, comments, and URLs. */
198
+ function convertPixelTokens(value, rootValue) {
199
+ let result = '';
200
+ let cursor = 0;
201
+ while (cursor < value.length) {
202
+ const char = value[cursor];
203
+ if (char === '"' || char === "'") {
204
+ const end = quotedEnd(value, cursor);
205
+ result += value.slice(cursor, end);
206
+ cursor = end;
207
+ continue;
208
+ }
209
+ if (value.startsWith('/*', cursor)) {
210
+ const closing = value.indexOf('*/', cursor + 2);
211
+ const end = closing < 0 ? value.length : closing + 2;
212
+ result += value.slice(cursor, end);
213
+ cursor = end;
214
+ continue;
215
+ }
216
+ const protectedUrlEnd = urlEnd(value, cursor);
217
+ if (protectedUrlEnd !== undefined) {
218
+ result += value.slice(cursor, protectedUrlEnd);
219
+ cursor = protectedUrlEnd;
220
+ continue;
221
+ }
222
+ const canStartPixel = /[\d.]/.test(char)
223
+ || (char === '-' && /[\d.]/.test(value[cursor + 1] ?? ''));
224
+ const pixel = canStartPixel && !isIdentifierChar(value[cursor - 1])
225
+ ? /^(-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)px/i.exec(value.slice(cursor))
226
+ : null;
227
+ const pixelEnd = pixel ? cursor + pixel[0].length : cursor;
228
+ if (pixel && !isIdentifierChar(value[pixelEnd])) {
229
+ result += `${formatRem(Number(pixel[1]) / rootValue)}rem`;
230
+ cursor += pixel[0].length;
231
+ continue;
232
+ }
233
+ result += char;
234
+ cursor++;
235
+ }
236
+ return result;
237
+ }
238
+ function createResponsiveContext(isMobile, responsive) {
239
+ const mobile = responsive?.mobile ?? DEFAULT_MOBILE_RESPONSIVE;
240
+ const unit = mobile.unit;
241
+ const rootValue = mobile.rootValue;
242
+ if (!Number.isFinite(rootValue) || rootValue <= 0) {
243
+ throw new Error('[renderCard] responsive.mobile.rootValue must be a positive number');
244
+ }
245
+ const active = isMobile && unit === 'rem';
246
+ const context = {
247
+ active,
248
+ rootValue,
249
+ resolveLength(value) {
250
+ if (typeof value === 'number') {
251
+ return active
252
+ ? `${formatRem(value / rootValue)}rem`
253
+ : `${value}px`;
254
+ }
255
+ return active ? convertPixelTokens(value, rootValue) : value;
256
+ },
257
+ convertCSS(value) {
258
+ return active ? convertPixelTokens(value, rootValue) : value;
259
+ },
260
+ };
261
+ return Object.freeze(context);
262
+ }
263
+ /** Convert CSS declarations without touching element text or SVG geometry. */
264
+ function applyResponsiveStyles(root, context) {
265
+ if (!context.active)
266
+ return;
267
+ const isPreservedContentDescendant = (element) => {
268
+ const boundary = element.closest('[data-responsive-content="preserve"]');
269
+ if (boundary == null || boundary === element)
270
+ return false;
271
+ const boundaryBelongsToRoot = root instanceof Element
272
+ ? root.contains(boundary)
273
+ : boundary.getRootNode() === root;
274
+ return boundaryBelongsToRoot;
275
+ };
276
+ const convertElement = (element) => {
277
+ if (isPreservedContentDescendant(element))
278
+ return;
279
+ if (element instanceof SVGElement
280
+ && !element.classList.contains('icon-svg'))
281
+ return;
282
+ const cssText = element.getAttribute('style');
283
+ if (cssText?.includes('px')) {
284
+ element.setAttribute('style', context.convertCSS(cssText));
285
+ }
286
+ };
287
+ if (root instanceof HTMLElement)
288
+ convertElement(root);
289
+ root.querySelectorAll('[style]').forEach(convertElement);
290
+ root.querySelectorAll('style').forEach((style) => {
291
+ if (isPreservedContentDescendant(style))
292
+ return;
293
+ if (style.textContent?.includes('px')) {
294
+ style.textContent = context.convertCSS(style.textContent);
295
+ }
296
+ });
297
+ }
298
+
99
299
  /**
100
300
  * Chart renderer using ECharts (tree-shaken import).
101
301
  * Supports: line, bar, pie/donut, scatter, funnel, heatmap.
@@ -1459,6 +1659,7 @@ class BaseElement extends HTMLElement {
1459
1659
  this._node = null;
1460
1660
  this._props = {};
1461
1661
  this._isMobile = false;
1662
+ this._responsive = createResponsiveContext(false);
1462
1663
  this.attachShadow({ mode: 'open' });
1463
1664
  }
1464
1665
  // ─── Data Interface ─────────────────────────────────────────
@@ -1466,10 +1667,12 @@ class BaseElement extends HTMLElement {
1466
1667
  * Set component data and trigger render.
1467
1668
  * Called by the component renderer (from `renderCard` pipeline).
1468
1669
  */
1469
- setData(node, props, isMobile) {
1670
+ setData(node, props, isMobile, responsive) {
1470
1671
  this._node = node;
1471
1672
  this._props = props;
1472
1673
  this._isMobile = isMobile;
1674
+ this._responsiveOptions = responsive;
1675
+ this._responsive = createResponsiveContext(isMobile, responsive);
1473
1676
  // Expose identity on the host element for querying / debugging
1474
1677
  this.setAttribute('data-card-id', node.id);
1475
1678
  this.setAttribute('data-card-type', node.type);
@@ -1478,10 +1681,13 @@ class BaseElement extends HTMLElement {
1478
1681
  /**
1479
1682
  * Update props only (e.g. on variable change + re-render).
1480
1683
  */
1481
- updateProps(props, isMobile) {
1684
+ updateProps(props, isMobile, responsive) {
1482
1685
  this._props = props;
1483
1686
  if (isMobile !== undefined)
1484
1687
  this._isMobile = isMobile;
1688
+ if (responsive !== undefined)
1689
+ this._responsiveOptions = responsive;
1690
+ this._responsive = createResponsiveContext(this._isMobile, this._responsiveOptions);
1485
1691
  this.render();
1486
1692
  }
1487
1693
  // ─── Helpers ────────────────────────────────────────────────
@@ -1501,11 +1707,18 @@ class BaseElement extends HTMLElement {
1501
1707
  const processed = isExpressionResult
1502
1708
  ? style
1503
1709
  : this.resolveSizeInStyle(style);
1504
- return this.escapeAttribute(buildStyleString(processed));
1710
+ return this.escapeAttribute(this._responsive.convertCSS(buildStyleString(processed)));
1505
1711
  }
1506
1712
  /** Resolve a single size value (number → px). */
1507
1713
  toCSS(value) {
1508
- return resolveSize(value);
1714
+ return this._responsive.resolveLength(value);
1715
+ }
1716
+ /** Assign component markup and convert only its CSS declarations. */
1717
+ setShadowHTML(html) {
1718
+ if (!this.shadowRoot)
1719
+ return;
1720
+ this.shadowRoot.innerHTML = html;
1721
+ applyResponsiveStyles(this.shadowRoot, this._responsive);
1509
1722
  }
1510
1723
  /**
1511
1724
  * Escape a value before interpolating it into a double-quoted HTML
@@ -1531,13 +1744,13 @@ class BaseElement extends HTMLElement {
1531
1744
  // line-height is ambiguous: numbers < 4 are CSS multipliers
1532
1745
  // (1.5 = 1.5×font-size), larger numbers keep the SDK-wide
1533
1746
  // number→px convention (22 = 22px) for schema compat.
1534
- resolved[key] = value < 4 ? value : resolveSize(value);
1747
+ resolved[key] = value < 4 ? value : this.toCSS(value);
1535
1748
  }
1536
1749
  else if (BaseElement.UNITLESS_PROPS.has(prop)) {
1537
1750
  resolved[key] = value;
1538
1751
  }
1539
1752
  else {
1540
- resolved[key] = resolveSize(value);
1753
+ resolved[key] = this.toCSS(value);
1541
1754
  }
1542
1755
  }
1543
1756
  return resolved;
@@ -1600,7 +1813,7 @@ class CardText extends BaseElement {
1600
1813
  this._tooltipTimer = 0;
1601
1814
  }
1602
1815
  // ─── setData override: reset streaming state on re-bindé ─────
1603
- setData(node, props, isMobile) {
1816
+ setData(node, props, isMobile, responsive) {
1604
1817
  // Reset streaming state so full re-render is triggered
1605
1818
  if (this._streamTimer) {
1606
1819
  clearInterval(this._streamTimer);
@@ -1611,12 +1824,7 @@ class CardText extends BaseElement {
1611
1824
  this._displayedContent = '';
1612
1825
  this._contentEl = null;
1613
1826
  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();
1827
+ super.setData(node, props, isMobile, responsive);
1620
1828
  }
1621
1829
  render() {
1622
1830
  if (!this.shadowRoot || !this._node)
@@ -1670,7 +1878,7 @@ class CardText extends BaseElement {
1670
1878
  : rawText;
1671
1879
  // Use <span> for plain text (legacy compat), <div> for markdown (block elements)
1672
1880
  const tag = isMarkdown || isStreaming ? 'div' : 'span';
1673
- this.shadowRoot.innerHTML = `
1881
+ this.setShadowHTML(`
1674
1882
  <style>
1675
1883
  :host {
1676
1884
  display: inline-block;
@@ -1782,8 +1990,8 @@ class CardText extends BaseElement {
1782
1990
  <${tag}
1783
1991
  class="card-text ${isMobile ? 'card-mobile' : 'card-desktop'}${maxLines ? ' clamped' : ''}"
1784
1992
  style="${combinedStyle}"
1785
- ><span class="card-text-content">${initialHTML}</span>${isStreaming ? '<span class="streaming-cursor"></span>' : ''}</${tag}>
1786
- `;
1993
+ ><span class="card-text-content" data-responsive-content="preserve">${initialHTML}</span>${isStreaming ? '<span class="streaming-cursor"></span>' : ''}</${tag}>
1994
+ `);
1787
1995
  // Cache DOM references for streaming updates
1788
1996
  this._contentEl = this.shadowRoot.querySelector('.card-text-content');
1789
1997
  this._cursorEl = this.shadowRoot.querySelector('.streaming-cursor');
@@ -1907,7 +2115,12 @@ class CardText extends BaseElement {
1907
2115
  * Remove cursor when streaming ends.
1908
2116
  * Called externally when streaming prop changes to false.
1909
2117
  */
1910
- updateProps(props, isMobile) {
2118
+ updateProps(props, isMobile, responsive) {
2119
+ if (isMobile !== undefined)
2120
+ this._isMobile = isMobile;
2121
+ if (responsive !== undefined)
2122
+ this._responsiveOptions = responsive;
2123
+ this._responsive = createResponsiveContext(this._isMobile, this._responsiveOptions);
1911
2124
  const wasStreaming = this._props.streaming === true || this._props.streaming === 'true';
1912
2125
  const willStream = props.streaming === true || props.streaming === 'true';
1913
2126
  // If streaming just ended, remove cursor and stop animation
@@ -1928,13 +2141,9 @@ class CardText extends BaseElement {
1928
2141
  : this._escapeHTML(rawText);
1929
2142
  }
1930
2143
  this._props = props;
1931
- if (isMobile !== undefined)
1932
- this._isMobile = isMobile;
1933
2144
  return;
1934
2145
  }
1935
2146
  this._props = props;
1936
- if (isMobile !== undefined)
1937
- this._isMobile = isMobile;
1938
2147
  this.render();
1939
2148
  }
1940
2149
  // ─── Helpers ──────────────────────────────────────────────────
@@ -2141,7 +2350,7 @@ class CardButton extends BaseElement {
2141
2350
  large: { padding: '12px 28px', fontSize: '16px' },
2142
2351
  };
2143
2352
  const sizeConfig = sizeMap[size] ?? sizeMap.medium;
2144
- this.shadowRoot.innerHTML = `
2353
+ this.setShadowHTML(`
2145
2354
  <style>
2146
2355
  :host {
2147
2356
  display: ${block ? 'block' : 'inline-block'};
@@ -2286,7 +2495,7 @@ class CardButton extends BaseElement {
2286
2495
  ${disabled ? 'disabled' : ''}
2287
2496
  style="${inlineStyle}"
2288
2497
  ><span class="card-btn-content">${icon ? `<span class="card-btn-icon">${sanitizeIconHtml(String(icon))}</span>` : ''}${displayText}</span></button>
2289
- `;
2498
+ `);
2290
2499
  }
2291
2500
  }
2292
2501
  CardButton.is = 'ai-card-button';
@@ -2317,20 +2526,40 @@ function sanitizeImageSrc(src) {
2317
2526
  return '';
2318
2527
  return trimmed;
2319
2528
  }
2529
+ function normalizeIconSize(value) {
2530
+ if (typeof value === 'number') {
2531
+ const size = Number.isFinite(value) && value > 0 ? value : 24;
2532
+ return { css: `${size}px`, intrinsic: size };
2533
+ }
2534
+ const raw = String(value ?? '').trim();
2535
+ if (/^(?:\d+\.?\d*|\.\d+)$/.test(raw)) {
2536
+ const size = Number(raw) || 24;
2537
+ return { css: `${size}px`, intrinsic: size };
2538
+ }
2539
+ if (/^(?:\d+\.?\d*|\.\d+)(?:px|rem|em|%|vw|vh|vmin|vmax|ch|ex|cm|mm|in|pt|pc)$/.test(raw)) {
2540
+ return { css: raw };
2541
+ }
2542
+ return { css: '24px', intrinsic: 24 };
2543
+ }
2320
2544
  function renderIconContent(input) {
2321
- const size = Number(input.size) || 24;
2545
+ const normalizedSize = normalizeIconSize(input.size);
2546
+ const size = normalizedSize.css;
2322
2547
  const name = input.name == null ? undefined : String(input.name);
2548
+ const sizeStyle = `width:${escapeAttr$1(size)};height:${escapeAttr$1(size)};`;
2549
+ const intrinsicAttrs = normalizedSize.intrinsic == null
2550
+ ? ''
2551
+ : `width="${normalizedSize.intrinsic}" height="${normalizedSize.intrinsic}" `;
2323
2552
  if (input.src) {
2324
2553
  return `<img class="icon-img" src="${escapeAttr$1(sanitizeImageSrc(input.src))}" ` +
2325
- `width="${size}" height="${size}" alt="${escapeAttr$1(name ?? 'icon')}" />`;
2554
+ `${intrinsicAttrs}style="${sizeStyle}" alt="${escapeAttr$1(name ?? 'icon')}" />`;
2326
2555
  }
2327
2556
  const icon = getBuiltinIcon(name);
2328
2557
  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'))}" ` +
2558
+ return `<svg class="icon-svg" viewBox="${escapeAttr$1(icon.viewBox)}" ${intrinsicAttrs}style="${sizeStyle}" ` +
2559
+ `color="${escapeAttr$1(String(input.color ?? 'currentColor'))}" ` +
2331
2560
  `aria-hidden="true">${icon.body}</svg>`;
2332
2561
  }
2333
- return `<span class="icon-text" style="font-size:${size}px; line-height:1;">` +
2562
+ return `<span class="icon-text" style="font-size:${escapeAttr$1(size)};line-height:1;">` +
2334
2563
  `${escapeText$1(name ?? '?')}</span>`;
2335
2564
  }
2336
2565
 
@@ -2542,7 +2771,7 @@ class CardInput extends BaseElement {
2542
2771
  ${numberAttributes}
2543
2772
  style="${nativeInlineStyle}"
2544
2773
  />`;
2545
- this.shadowRoot.innerHTML = `
2774
+ this.setShadowHTML(`
2546
2775
  <style>
2547
2776
  :host {
2548
2777
  display: block;
@@ -2716,7 +2945,7 @@ class CardInput extends BaseElement {
2716
2945
  ${numberStepperHtml}
2717
2946
  </div>
2718
2947
  </div>
2719
- `;
2948
+ `);
2720
2949
  // Wire up native input/change events that bubble out of Shadow DOM.
2721
2950
  // renderCard listens for these standard event names (mapped from onInput / onChange).
2722
2951
  // The `detail.value` carries the current input value so that:
@@ -2839,7 +3068,7 @@ class CardImage extends BaseElement {
2839
3068
  imgStyles.push(`object-fit:${objectFit}`);
2840
3069
  if (inlineStyle)
2841
3070
  imgStyles.push(inlineStyle);
2842
- this.shadowRoot.innerHTML = `
3071
+ this.setShadowHTML(`
2843
3072
  <style>
2844
3073
  :host {
2845
3074
  display: inline-block;
@@ -2948,7 +3177,7 @@ class CardImage extends BaseElement {
2948
3177
  <button class="lightbox-close" aria-label="Close">&times;</button>
2949
3178
  <img class="lightbox-image" src="${imgSrc}" alt="${imgAlt}" />
2950
3179
  </div>
2951
- `;
3180
+ `);
2952
3181
  this.bindEvents(preview);
2953
3182
  }
2954
3183
  bindEvents(preview) {
@@ -3040,7 +3269,7 @@ class CardDivider extends BaseElement {
3040
3269
  const isVertical = direction === 'vertical';
3041
3270
  const borderStyle = dashed ? 'dashed' : 'solid';
3042
3271
  const displayText = text ? this.resolveContent(text) : '';
3043
- this.shadowRoot.innerHTML = `
3272
+ this.setShadowHTML(`
3044
3273
  <style>
3045
3274
  :host {
3046
3275
  display: ${isVertical ? 'inline-flex' : 'flex'};
@@ -3064,7 +3293,7 @@ class CardDivider extends BaseElement {
3064
3293
  </style>
3065
3294
  <div class="divider-line"></div>
3066
3295
  ${displayText ? `<span class="divider-text">${displayText}</span><div class="divider-line"></div>` : ''}
3067
- `;
3296
+ `);
3068
3297
  if (inlineStyle) {
3069
3298
  this.style.cssText += ';' + inlineStyle;
3070
3299
  }
@@ -3176,7 +3405,7 @@ class CardRate extends BaseElement {
3176
3405
  return `<span class="star ${isFull ? 'filled' : ''}" data-value="${starIndex}"
3177
3406
  style="${starInline};${itemInline}">★</span>`;
3178
3407
  }).join('');
3179
- this.shadowRoot.innerHTML = `
3408
+ this.setShadowHTML(`
3180
3409
  <style>
3181
3410
  :host {
3182
3411
  display: inline-flex;
@@ -3234,7 +3463,7 @@ class CardRate extends BaseElement {
3234
3463
  aria-readonly="${Boolean(readOnly)}"
3235
3464
  style="${inlineStyle}"
3236
3465
  >${stars}</div>
3237
- `;
3466
+ `);
3238
3467
  if (!interactive)
3239
3468
  return;
3240
3469
  // Bind click handlers
@@ -3383,7 +3612,7 @@ class CardCounter extends BaseElement {
3383
3612
  const numInline = this.buildInlineStyle(valueStyle, isExpressionResultStyle);
3384
3613
  const minusContent = this.renderIcon(minusIcon, this.defaultMinusGlyph(btnSize), btnSize);
3385
3614
  const plusContent = this.renderIcon(plusIcon, this.defaultPlusGlyph(btnSize), btnSize);
3386
- this.shadowRoot.innerHTML = `
3615
+ this.setShadowHTML(`
3387
3616
  <style>
3388
3617
  :host {
3389
3618
  display: inline-flex;
@@ -3458,7 +3687,7 @@ class CardCounter extends BaseElement {
3458
3687
  ${plusDisabled ? 'disabled' : ''} aria-label="${this.escapeAttr(String(increaseAriaLabel))}"
3459
3688
  style="${plusInline}">${plusContent}</button>
3460
3689
  </div>
3461
- `;
3690
+ `);
3462
3691
  if (disabled || readOnly)
3463
3692
  return;
3464
3693
  const minusBtn = this.shadowRoot.querySelector('.c-minus');
@@ -3573,7 +3802,7 @@ class CardTag extends BaseElement {
3573
3802
  const bgColor = isCustomColor ? color + '1a' : preset.bg;
3574
3803
  const borderColor = isCustomColor ? color : preset.border;
3575
3804
  const textColor = isCustomColor ? color : preset.text;
3576
- this.shadowRoot.innerHTML = `
3805
+ this.setShadowHTML(`
3577
3806
  <style>
3578
3807
  :host {
3579
3808
  display: inline-flex;
@@ -3609,7 +3838,7 @@ class CardTag extends BaseElement {
3609
3838
  ${text}
3610
3839
  ${closable ? '<span class="close-btn">✕</span>' : ''}
3611
3840
  </span>
3612
- `;
3841
+ `);
3613
3842
  if (closable) {
3614
3843
  this.shadowRoot.querySelector('.close-btn')?.addEventListener('click', () => {
3615
3844
  this.dispatchEvent(new CustomEvent('close', {
@@ -3664,7 +3893,7 @@ class CardSelect extends BaseElement {
3664
3893
  render() {
3665
3894
  if (!this.shadowRoot || !this._node)
3666
3895
  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;
3896
+ 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
3897
  const propValue = String(externalValue ?? defaultValue ?? '');
3669
3898
  const resolvedSize = (['small', 'medium', 'large'].includes(size)
3670
3899
  ? size
@@ -3702,6 +3931,7 @@ class CardSelect extends BaseElement {
3702
3931
  optionPadding: '5px 12px',
3703
3932
  },
3704
3933
  }[resolvedSize];
3934
+ const resolvedArrowSize = arrowSize ?? sizePreset.arrowSize;
3705
3935
  const variantPreset = {
3706
3936
  outlined: {
3707
3937
  background: '#fff',
@@ -3725,13 +3955,14 @@ class CardSelect extends BaseElement {
3725
3955
  this.applyHostStyles(style, isExpressionResultStyle, String(accentColor));
3726
3956
  const triggerInline = this.buildInlineStyle(semanticStyles.trigger, isExpressionResultStyle);
3727
3957
  const dropdownInline = this.buildInlineStyle(semanticStyles.dropdown, isExpressionResultStyle);
3958
+ const arrowInline = this.buildInlineStyle(semanticStyles.arrow, isExpressionResultStyle);
3728
3959
  const selectedOption = options.find((o) => o.value === value);
3729
3960
  const displayText = selectedOption ? selectedOption.label : placeholder;
3730
3961
  const isPlaceholder = !selectedOption;
3731
3962
  const openClass = this._open ? 'open' : '';
3732
3963
  const arrowIcon = renderIconContent({
3733
3964
  name: 'caret_down',
3734
- size: sizePreset.arrowSize,
3965
+ size: this.toCSS(resolvedArrowSize),
3735
3966
  color: 'currentColor',
3736
3967
  });
3737
3968
  const listboxId = `${this._node.id}-listbox`;
@@ -3740,7 +3971,7 @@ class CardSelect extends BaseElement {
3740
3971
  && this._activeIndex < options.length)
3741
3972
  ? `${this._node.id}-option-${this._activeIndex}`
3742
3973
  : '';
3743
- this.shadowRoot.innerHTML = `
3974
+ this.setShadowHTML(`
3744
3975
  <style>
3745
3976
  :host {
3746
3977
  display: inline-block;
@@ -3797,8 +4028,8 @@ class CardSelect extends BaseElement {
3797
4028
  flex: none;
3798
4029
  align-items: center;
3799
4030
  justify-content: center;
3800
- width: ${sizePreset.arrowSize}px;
3801
- height: ${sizePreset.arrowSize}px;
4031
+ width: ${this.toCSS(resolvedArrowSize)};
4032
+ height: ${this.toCSS(resolvedArrowSize)};
3802
4033
  margin-left: 8px;
3803
4034
  color: ${disabled ? '#bfbfbf' : '#999'};
3804
4035
  transition: transform 0.2s;
@@ -3885,7 +4116,7 @@ class CardSelect extends BaseElement {
3885
4116
  style="${triggerInline}"
3886
4117
  >
3887
4118
  <span class="select-text">${this.escapeText(String(displayText))}</span>
3888
- <span class="select-arrow ${openClass}">${arrowIcon}</span>
4119
+ <span class="select-arrow ${openClass}" style="${arrowInline}">${arrowIcon}</span>
3889
4120
  </div>
3890
4121
  <div
3891
4122
  class="dropdown ${openClass}"
@@ -3911,7 +4142,7 @@ class CardSelect extends BaseElement {
3911
4142
  ><span class="option-label">${this.escapeText(String(opt.label))}</span></div>`);
3912
4143
  }).join('')}
3913
4144
  </div>
3914
- `;
4145
+ `);
3915
4146
  if (disabled)
3916
4147
  return;
3917
4148
  const trigger = this.shadowRoot.querySelector('.select-trigger');
@@ -3938,13 +4169,13 @@ class CardSelect extends BaseElement {
3938
4169
  disconnectedCallback() {
3939
4170
  document.removeEventListener('click', this._onDocClick);
3940
4171
  }
3941
- updateProps(props, isMobile) {
4172
+ updateProps(props, isMobile, responsive) {
3942
4173
  if (Object.prototype.hasOwnProperty.call(props, 'value')
3943
4174
  || Object.prototype.hasOwnProperty.call(props, 'defaultValue')) {
3944
4175
  this._localValue = null;
3945
4176
  this._propValue = undefined;
3946
4177
  }
3947
- super.updateProps(props, isMobile);
4178
+ super.updateProps(props, isMobile, responsive);
3948
4179
  }
3949
4180
  _setOpen(open) {
3950
4181
  if (this._open === open)
@@ -4092,7 +4323,7 @@ class CardPasscodeInput extends BaseElement {
4092
4323
  autocomplete="one-time-code"
4093
4324
  ${disabled ? 'disabled' : ''}
4094
4325
  />`).join('');
4095
- this.shadowRoot.innerHTML = `
4326
+ this.setShadowHTML(`
4096
4327
  <style>
4097
4328
  :host {
4098
4329
  display: inline-flex;
@@ -4128,7 +4359,7 @@ class CardPasscodeInput extends BaseElement {
4128
4359
  }
4129
4360
  </style>
4130
4361
  <div class="passcode-wrapper">${inputs}</div>
4131
- `;
4362
+ `);
4132
4363
  if (disabled)
4133
4364
  return;
4134
4365
  const cells = this.shadowRoot.querySelectorAll('.passcode-cell');
@@ -4211,10 +4442,10 @@ class CardIcon extends BaseElement {
4211
4442
  const iconContent = renderIconContent({
4212
4443
  name,
4213
4444
  src,
4214
- size: iconSize,
4445
+ size: this.toCSS(iconSize),
4215
4446
  color,
4216
4447
  });
4217
- this.shadowRoot.innerHTML = `
4448
+ this.setShadowHTML(`
4218
4449
  <style>
4219
4450
  :host {
4220
4451
  display: inline-flex;
@@ -4237,7 +4468,7 @@ class CardIcon extends BaseElement {
4237
4468
  }
4238
4469
  </style>
4239
4470
  <span class="icon-wrapper" style="${inlineStyle}">${iconContent}</span>
4240
- `;
4471
+ `);
4241
4472
  }
4242
4473
  }
4243
4474
  CardIcon.is = 'ai-card-icon';
@@ -4319,7 +4550,7 @@ class CardForm extends BaseElement {
4319
4550
  const fieldItems = fields
4320
4551
  .map((field, index) => this._renderField(field, index))
4321
4552
  .join('');
4322
- this.shadowRoot.innerHTML = `
4553
+ this.setShadowHTML(`
4323
4554
  <style>
4324
4555
  :host {
4325
4556
  display: block;
@@ -4479,7 +4710,7 @@ class CardForm extends BaseElement {
4479
4710
  ${fieldItems}
4480
4711
  <button class="submit-btn" ${disabled ? 'disabled' : ''} style="${isHorizontal ? `margin-left: 84px;` : ''}">${submitText}</button>
4481
4712
  </div>
4482
- `;
4713
+ `);
4483
4714
  this._initializeSelectFields(fields, disabled);
4484
4715
  this._initializeRateFields(fields, disabled);
4485
4716
  this._bindEvents(fields, disabled);
@@ -4600,7 +4831,7 @@ class CardForm extends BaseElement {
4600
4831
  events: undefined,
4601
4832
  directives: undefined,
4602
4833
  };
4603
- selectElement.setData(node, props, this._isMobile);
4834
+ selectElement.setData(node, props, this._isMobile, this._responsiveOptions);
4604
4835
  const label = selectElement
4605
4836
  .closest('.form-field')
4606
4837
  ?.querySelector('.field-label');
@@ -4653,7 +4884,7 @@ class CardForm extends BaseElement {
4653
4884
  events: undefined,
4654
4885
  directives: undefined,
4655
4886
  };
4656
- rateElement.setData(node, props, this._isMobile);
4887
+ rateElement.setData(node, props, this._isMobile, this._responsiveOptions);
4657
4888
  const label = rateElement
4658
4889
  .closest('.form-field')
4659
4890
  ?.querySelector('.field-label');
@@ -4964,7 +5195,7 @@ class CardLoading extends BaseElement {
4964
5195
  const displayText = text ? this.resolveContent(text) : '';
4965
5196
  const px = typeof size === 'number' ? `${size}px` : size;
4966
5197
  const dur = typeof duration === 'number' ? `${duration}s` : duration;
4967
- this.shadowRoot.innerHTML = `
5198
+ this.setShadowHTML(`
4968
5199
  <style>
4969
5200
  :host {
4970
5201
  display: inline-flex;
@@ -4993,7 +5224,7 @@ class CardLoading extends BaseElement {
4993
5224
  </style>
4994
5225
  <div class="spinner"></div>
4995
5226
  ${displayText ? `<span class="loading-text">${displayText}</span>` : ''}
4996
- `;
5227
+ `);
4997
5228
  if (inlineStyle) {
4998
5229
  this.style.cssText += ';' + inlineStyle;
4999
5230
  }
@@ -5185,6 +5416,7 @@ class CardProgress extends BaseElement {
5185
5416
  track.setAttribute('aria-valuetext', valueText);
5186
5417
  }
5187
5418
  this.shadowRoot.replaceChildren(styleElement, wrapper);
5419
+ applyResponsiveStyles(this.shadowRoot, this._responsive);
5188
5420
  }
5189
5421
  buildLegend(segments, position, style, isExpressionResultStyle) {
5190
5422
  const labeled = segments.filter((segment) => segment.label?.trim());
@@ -5378,7 +5610,7 @@ class CardSteps extends BaseElement {
5378
5610
  </div>
5379
5611
  `;
5380
5612
  }).join('');
5381
- this.shadowRoot.innerHTML = `
5613
+ this.setShadowHTML(`
5382
5614
  <style>
5383
5615
  :host {
5384
5616
  display: block;
@@ -5483,7 +5715,7 @@ class CardSteps extends BaseElement {
5483
5715
  }
5484
5716
  </style>
5485
5717
  <div class="steps-wrapper" style="${inlineStyle}">${stepsHTML}</div>
5486
- `;
5718
+ `);
5487
5719
  // Bind click only on clickable steps → dispatch `step-click` event
5488
5720
  this.shadowRoot.querySelectorAll('.step-clickable').forEach((el) => {
5489
5721
  el.addEventListener('click', (e) => {
@@ -5577,10 +5809,12 @@ class CardCollapse extends BaseElement {
5577
5809
  // Arrow HTML: custom image or built-in SVG
5578
5810
  const arrowHTML = arrowIconUrl
5579
5811
  ? `<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 = `
5812
+ : renderIconContent({
5813
+ name: 'caret_down',
5814
+ size: this.toCSS(12),
5815
+ color: 'currentColor',
5816
+ });
5817
+ this.setShadowHTML(`
5584
5818
  <style>
5585
5819
  :host {
5586
5820
  display: block;
@@ -5703,11 +5937,13 @@ class CardCollapse extends BaseElement {
5703
5937
  </span>
5704
5938
  </div>
5705
5939
  <div class="collapse-body ${this._expanded ? 'expanded' : ''}" style="${bodyInlineStyle}">
5706
- <div class="collapse-content" style="${contentInlineStyle}">${bodyHTML ? bodyHTML : ''}</div>
5940
+ <div class="collapse-content" style="${contentInlineStyle}">
5941
+ <div class="collapse-markdown" data-responsive-content="preserve">${bodyHTML ? bodyHTML : ''}</div>
5942
+ </div>
5707
5943
  <div class="collapse-slot"><slot></slot></div>
5708
5944
  </div>
5709
5945
  </div>
5710
- `;
5946
+ `);
5711
5947
  // Bind click event
5712
5948
  const header = this.shadowRoot.querySelector('.collapse-header');
5713
5949
  header?.addEventListener('click', () => this._toggle());
@@ -5879,7 +6115,7 @@ class CardHtml extends BaseElement {
5879
6115
  }
5880
6116
  if (inlineStyle)
5881
6117
  wrapperStyles.push(inlineStyle);
5882
- this.shadowRoot.innerHTML = `
6118
+ this.setShadowHTML(`
5883
6119
  <style>
5884
6120
  /* Functional styles ONLY — content presentation is owned by the HTML
5885
6121
  itself (browser semantic defaults + the model's inline styles).
@@ -5899,9 +6135,10 @@ class CardHtml extends BaseElement {
5899
6135
  </style>
5900
6136
  <div
5901
6137
  class="card-html ${isMobile ? 'card-mobile' : 'card-desktop'}"
6138
+ data-responsive-content="preserve"
5902
6139
  style="${wrapperStyles.join(';')}"
5903
6140
  >${safeHtml}</div>
5904
- `;
6141
+ `);
5905
6142
  // Restore scroll state after replacement (only relevant with maxHeight)
5906
6143
  if (maxHeight != null) {
5907
6144
  const wrapper = this.shadowRoot.querySelector('.card-html');
@@ -6072,7 +6309,7 @@ class CardDatePicker extends BaseElement {
6072
6309
  displayText = showTodayPrefix && isToday(date) ? `${todayText}, ${formatted}` : formatted;
6073
6310
  }
6074
6311
  const hostWidth = width != null && width !== '' ? `width: ${this.toCSS(width)};` : '';
6075
- this.shadowRoot.innerHTML = `
6312
+ this.setShadowHTML(`
6076
6313
  <style>
6077
6314
  :host {
6078
6315
  display: inline-block;
@@ -6175,7 +6412,7 @@ class CardDatePicker extends BaseElement {
6175
6412
  <span class="trigger-arrow ${this._open ? 'open' : ''}">${CHEVRON_ICON}</span>
6176
6413
  </div>
6177
6414
  <div class="panel ${this._open ? 'open' : ''}"><div class="panel-calendar"></div></div>
6178
- `;
6415
+ `);
6179
6416
  if (disabled)
6180
6417
  return;
6181
6418
  const trigger = this.shadowRoot.querySelector('.trigger');
@@ -6407,7 +6644,7 @@ class CardAudio extends BaseElement {
6407
6644
  const valid = isSafeMediaSrc(src);
6408
6645
  // Audible autoplay is blocked by browsers — only ever autoplay muted
6409
6646
  const effectiveMuted = muted || autoplay;
6410
- this.shadowRoot.innerHTML = `
6647
+ this.setShadowHTML(`
6411
6648
  <style>
6412
6649
  :host {
6413
6650
  display: block;
@@ -6438,7 +6675,7 @@ class CardAudio extends BaseElement {
6438
6675
  style="${inlineStyle}"
6439
6676
  ></audio>`
6440
6677
  : `<div class="placeholder" style="${inlineStyle}">暂无可播放的媒体源</div>`}
6441
- `;
6678
+ `);
6442
6679
  const media = this.shadowRoot.querySelector('audio');
6443
6680
  if (media) {
6444
6681
  // The `muted` content attribute only applies before load — set the
@@ -6492,7 +6729,7 @@ class CardVideo extends BaseElement {
6492
6729
  const videoHeight = height != null && height !== '' ? `height: ${this.toCSS(height)};` : '';
6493
6730
  const valid = isSafeMediaSrc(src);
6494
6731
  const effectiveMuted = muted || autoplay;
6495
- this.shadowRoot.innerHTML = `
6732
+ this.setShadowHTML(`
6496
6733
  <style>
6497
6734
  :host {
6498
6735
  display: block;
@@ -6530,7 +6767,7 @@ class CardVideo extends BaseElement {
6530
6767
  style="${inlineStyle}"
6531
6768
  ></video>`
6532
6769
  : `<div class="placeholder" style="${inlineStyle}">暂无可播放的媒体源</div>`}
6533
- `;
6770
+ `);
6534
6771
  const media = this.shadowRoot.querySelector('video');
6535
6772
  if (media) {
6536
6773
  media.muted = effectiveMuted;
@@ -6574,7 +6811,7 @@ class CardSwitch extends BaseElement {
6574
6811
  ...(thumbStyle || {}),
6575
6812
  backgroundColor: thumbColor,
6576
6813
  }, isExpressionResultStyle);
6577
- this.shadowRoot.innerHTML = `
6814
+ this.setShadowHTML(`
6578
6815
  <style>
6579
6816
  :host {
6580
6817
  display: inline-flex;
@@ -6642,7 +6879,7 @@ class CardSwitch extends BaseElement {
6642
6879
  <span class="switch-thumb" part="thumb" style="${thumbInline}"></span>
6643
6880
  </button>
6644
6881
  </div>
6645
- `;
6882
+ `);
6646
6883
  const button = this.shadowRoot.querySelector('.switch-track');
6647
6884
  button?.addEventListener('click', () => {
6648
6885
  if (disabled || readOnly)
@@ -6761,7 +6998,7 @@ class CardChoiceItem extends BaseElement {
6761
6998
  this.setAttribute('data-selected', String(selected));
6762
6999
  this.toggleAttribute('data-disabled', fullyDisabled);
6763
7000
  this.toggleAttribute('data-selection-blocked', selectionBlocked);
6764
- this.shadowRoot.innerHTML = `
7001
+ this.setShadowHTML(`
6765
7002
  <style>
6766
7003
  :host {
6767
7004
  display: block;
@@ -6852,7 +7089,7 @@ class CardChoiceItem extends BaseElement {
6852
7089
  </div>
6853
7090
  ${indicatorPosition === 'end' ? this.renderIndicator(indicatorRole, selected, selectionUnavailable, tabIndex, ariaLabel, indicatorInline, checkedMark) : ''}
6854
7091
  </div>
6855
- `;
7092
+ `);
6856
7093
  const root = this.shadowRoot.querySelector('.choice-item');
6857
7094
  const indicator = this.shadowRoot.querySelector('.choice-indicator');
6858
7095
  indicator?.addEventListener('click', (event) => {
@@ -6993,7 +7230,7 @@ class CardChoiceList extends BaseElement {
6993
7230
  this.addEventListener('choice-request-change', this._onChoiceRequest);
6994
7231
  this.removeEventListener('choice-navigate', this._onChoiceNavigate);
6995
7232
  this.addEventListener('choice-navigate', this._onChoiceNavigate);
6996
- this.shadowRoot.innerHTML = `
7233
+ this.setShadowHTML(`
6997
7234
  <style>
6998
7235
  :host {
6999
7236
  display: block;
@@ -7016,7 +7253,7 @@ class CardChoiceList extends BaseElement {
7016
7253
  >
7017
7254
  <slot></slot>
7018
7255
  </div>
7019
- `;
7256
+ `);
7020
7257
  this.shadowRoot.querySelector('slot')?.addEventListener('slotchange', () => this.syncItems());
7021
7258
  queueMicrotask(() => this.syncItems());
7022
7259
  }
@@ -7227,159 +7464,159 @@ if (typeof customElements !== 'undefined'
7227
7464
  * Render a Text node as an `<ai-card-text>` Custom Element.
7228
7465
  * All text styling is scoped inside Shadow DOM.
7229
7466
  */
7230
- function renderText(node, props, isMobile) {
7467
+ function renderText(node, props, isMobile, responsive) {
7231
7468
  const el = document.createElement(CardText.is);
7232
- el.setData(node, props, isMobile);
7469
+ el.setData(node, props, isMobile, responsive);
7233
7470
  return el;
7234
7471
  }
7235
7472
  /**
7236
7473
  * Render a Button node as an `<ai-card-button>` Custom Element.
7237
7474
  * Supports variants (primary/secondary/text/danger), sizes, disabled state.
7238
7475
  */
7239
- function renderButton(node, props, isMobile) {
7476
+ function renderButton(node, props, isMobile, responsive) {
7240
7477
  const el = document.createElement(CardButton.is);
7241
- el.setData(node, props, isMobile);
7478
+ el.setData(node, props, isMobile, responsive);
7242
7479
  return el;
7243
7480
  }
7244
7481
  /**
7245
7482
  * Render an Input node as an `<ai-card-input>` Custom Element.
7246
7483
  * Supports text/password/number/textarea, label, placeholder, validation.
7247
7484
  */
7248
- function renderInput(node, props, isMobile) {
7485
+ function renderInput(node, props, isMobile, responsive) {
7249
7486
  const el = document.createElement(CardInput.is);
7250
- el.setData(node, props, isMobile);
7487
+ el.setData(node, props, isMobile, responsive);
7251
7488
  return el;
7252
7489
  }
7253
7490
  /**
7254
7491
  * Render an Image node as an `<ai-card-image>` Custom Element.
7255
7492
  * Supports click-to-zoom lightbox preview.
7256
7493
  */
7257
- function renderImage(node, props, isMobile) {
7494
+ function renderImage(node, props, isMobile, responsive) {
7258
7495
  const el = document.createElement(CardImage.is);
7259
- el.setData(node, props, isMobile);
7496
+ el.setData(node, props, isMobile, responsive);
7260
7497
  return el;
7261
7498
  }
7262
7499
  /** Render a Divider node. */
7263
- function renderDivider(node, props, isMobile) {
7500
+ function renderDivider(node, props, isMobile, responsive) {
7264
7501
  const el = document.createElement(CardDivider.is);
7265
- el.setData(node, props, isMobile);
7502
+ el.setData(node, props, isMobile, responsive);
7266
7503
  return el;
7267
7504
  }
7268
7505
  /** Render a Rate node. */
7269
- function renderRate(node, props, isMobile) {
7506
+ function renderRate(node, props, isMobile, responsive) {
7270
7507
  const el = document.createElement(CardRate.is);
7271
- el.setData(node, props, isMobile);
7508
+ el.setData(node, props, isMobile, responsive);
7272
7509
  return el;
7273
7510
  }
7274
7511
  /** Render a Counter node. */
7275
- function renderCounter(node, props, isMobile) {
7512
+ function renderCounter(node, props, isMobile, responsive) {
7276
7513
  const el = document.createElement(CardCounter.is);
7277
- el.setData(node, props, isMobile);
7514
+ el.setData(node, props, isMobile, responsive);
7278
7515
  return el;
7279
7516
  }
7280
7517
  /** Render a Tag node. */
7281
- function renderTag(node, props, isMobile) {
7518
+ function renderTag(node, props, isMobile, responsive) {
7282
7519
  const el = document.createElement(CardTag.is);
7283
- el.setData(node, props, isMobile);
7520
+ el.setData(node, props, isMobile, responsive);
7284
7521
  return el;
7285
7522
  }
7286
7523
  /** Render a Select node. */
7287
- function renderSelect(node, props, isMobile) {
7524
+ function renderSelect(node, props, isMobile, responsive) {
7288
7525
  const el = document.createElement(CardSelect.is);
7289
- el.setData(node, props, isMobile);
7526
+ el.setData(node, props, isMobile, responsive);
7290
7527
  return el;
7291
7528
  }
7292
7529
  /** Render a PasscodeInput node. */
7293
- function renderPasscodeInput(node, props, isMobile) {
7530
+ function renderPasscodeInput(node, props, isMobile, responsive) {
7294
7531
  const el = document.createElement(CardPasscodeInput.is);
7295
- el.setData(node, props, isMobile);
7532
+ el.setData(node, props, isMobile, responsive);
7296
7533
  return el;
7297
7534
  }
7298
7535
  /** Render an Icon node. */
7299
- function renderIcon(node, props, isMobile) {
7536
+ function renderIcon(node, props, isMobile, responsive) {
7300
7537
  const el = document.createElement(CardIcon.is);
7301
- el.setData(node, props, isMobile);
7538
+ el.setData(node, props, isMobile, responsive);
7302
7539
  return el;
7303
7540
  }
7304
7541
  /** Render a Form node. */
7305
- function renderForm(node, props, isMobile) {
7542
+ function renderForm(node, props, isMobile, responsive) {
7306
7543
  const el = document.createElement(CardForm.is);
7307
- el.setData(node, props, isMobile);
7544
+ el.setData(node, props, isMobile, responsive);
7308
7545
  return el;
7309
7546
  }
7310
7547
  /** Render a Loading node. */
7311
- function renderLoading(node, props, isMobile) {
7548
+ function renderLoading(node, props, isMobile, responsive) {
7312
7549
  const el = document.createElement(CardLoading.is);
7313
- el.setData(node, props, isMobile);
7550
+ el.setData(node, props, isMobile, responsive);
7314
7551
  return el;
7315
7552
  }
7316
7553
  /** Render a Progress node. */
7317
- function renderProgress(node, props, isMobile) {
7554
+ function renderProgress(node, props, isMobile, responsive) {
7318
7555
  const el = document.createElement(CardProgress.is);
7319
- el.setData(node, props, isMobile);
7556
+ el.setData(node, props, isMobile, responsive);
7320
7557
  return el;
7321
7558
  }
7322
7559
  /** Render a Steps node. */
7323
- function renderSteps(node, props, isMobile) {
7560
+ function renderSteps(node, props, isMobile, responsive) {
7324
7561
  const el = document.createElement(CardSteps.is);
7325
- el.setData(node, props, isMobile);
7562
+ el.setData(node, props, isMobile, responsive);
7326
7563
  return el;
7327
7564
  }
7328
7565
  /**
7329
7566
  * Render a Collapse node as an `<ai-card-collapse>` Custom Element.
7330
7567
  * Expandable/collapsible panel for AI thinking content.
7331
7568
  */
7332
- function renderCollapse(node, props, isMobile) {
7569
+ function renderCollapse(node, props, isMobile, responsive) {
7333
7570
  const el = document.createElement(CardCollapse.is);
7334
- el.setData(node, props, isMobile);
7571
+ el.setData(node, props, isMobile, responsive);
7335
7572
  return el;
7336
7573
  }
7337
7574
  /**
7338
7575
  * Render an Html node as an `<ai-card-html>` Custom Element.
7339
7576
  * Model-emitted HTML fragments, allow-list sanitized before display.
7340
7577
  */
7341
- function renderHtml(node, props, isMobile) {
7578
+ function renderHtml(node, props, isMobile, responsive) {
7342
7579
  const el = document.createElement(CardHtml.is);
7343
- el.setData(node, props, isMobile);
7580
+ el.setData(node, props, isMobile, responsive);
7344
7581
  return el;
7345
7582
  }
7346
7583
  /**
7347
7584
  * Render a DatePicker node as an `<ai-card-date-picker>` Custom Element.
7348
7585
  * Trigger + popup calendar panel (vanilla-calendar-pro).
7349
7586
  */
7350
- function renderDatePicker(node, props, isMobile) {
7587
+ function renderDatePicker(node, props, isMobile, responsive) {
7351
7588
  const el = document.createElement(CardDatePicker.is);
7352
- el.setData(node, props, isMobile);
7589
+ el.setData(node, props, isMobile, responsive);
7353
7590
  return el;
7354
7591
  }
7355
7592
  /** Render an Audio node as an `<ai-card-audio>` Custom Element. */
7356
- function renderAudio(node, props, isMobile) {
7593
+ function renderAudio(node, props, isMobile, responsive) {
7357
7594
  const el = document.createElement(CardAudio.is);
7358
- el.setData(node, props, isMobile);
7595
+ el.setData(node, props, isMobile, responsive);
7359
7596
  return el;
7360
7597
  }
7361
7598
  /** Render a Video node as an `<ai-card-video>` Custom Element. */
7362
- function renderVideo(node, props, isMobile) {
7599
+ function renderVideo(node, props, isMobile, responsive) {
7363
7600
  const el = document.createElement(CardVideo.is);
7364
- el.setData(node, props, isMobile);
7601
+ el.setData(node, props, isMobile, responsive);
7365
7602
  return el;
7366
7603
  }
7367
7604
  /** Render a Switch node. */
7368
- function renderSwitch(node, props, isMobile) {
7605
+ function renderSwitch(node, props, isMobile, responsive) {
7369
7606
  const el = document.createElement(CardSwitch.is);
7370
- el.setData(node, props, isMobile);
7607
+ el.setData(node, props, isMobile, responsive);
7371
7608
  return el;
7372
7609
  }
7373
7610
  /** Render a ChoiceList node. */
7374
- function renderChoiceList(node, props, isMobile) {
7611
+ function renderChoiceList(node, props, isMobile, responsive) {
7375
7612
  const el = document.createElement(CardChoiceList.is);
7376
- el.setData(node, props, isMobile);
7613
+ el.setData(node, props, isMobile, responsive);
7377
7614
  return el;
7378
7615
  }
7379
7616
  /** Render a ChoiceItem node. */
7380
- function renderChoiceItem(node, props, isMobile) {
7617
+ function renderChoiceItem(node, props, isMobile, responsive) {
7381
7618
  const el = document.createElement(CardChoiceItem.is);
7382
- el.setData(node, props, isMobile);
7619
+ el.setData(node, props, isMobile, responsive);
7383
7620
  return el;
7384
7621
  }
7385
7622
  /**
@@ -7389,7 +7626,7 @@ function renderChoiceItem(node, props, isMobile) {
7389
7626
  *
7390
7627
  * Layout is handled by the slot system in slots.ts (called from renderCard).
7391
7628
  */
7392
- function renderDefault(node, props, isMobile) {
7629
+ function renderDefault(node, props, isMobile, responsive) {
7393
7630
  const div = document.createElement('div');
7394
7631
  div.className = `card-element card-${node.type.toLowerCase()} ${isMobile ? 'card-mobile' : 'card-desktop'}`;
7395
7632
  div.setAttribute('data-card-id', node.id);
@@ -7401,6 +7638,7 @@ function renderDefault(node, props, isMobile) {
7401
7638
  if (resolvedStyle)
7402
7639
  div.style.cssText += ';' + resolvedStyle;
7403
7640
  }
7641
+ applyResponsiveStyles(div, createResponsiveContext(isMobile, responsive));
7404
7642
  return div;
7405
7643
  }
7406
7644
  // ─── Registry ────────────────────────────────────────────────────
@@ -7545,7 +7783,7 @@ function renderBoundCard(container, schema, options) {
7545
7783
  || resolved === 1);
7546
7784
  }
7547
7785
  const renderer = (componentRenderers[node.type] ?? componentRenderers._default);
7548
- const el = renderer(node, resolvedProps, isMobile);
7786
+ const el = renderer(node, resolvedProps, isMobile, options.responsive);
7549
7787
  if (isDisabled) {
7550
7788
  el.setAttribute('data-disabled', 'true');
7551
7789
  el.style.background = '#F5F5F5';
@@ -7608,6 +7846,7 @@ function renderBoundCard(container, schema, options) {
7608
7846
  htmlChild.style.setProperty('--card-disabled-color', '#C0C0C0');
7609
7847
  });
7610
7848
  }
7849
+ applyResponsiveStyles(el, createResponsiveContext(isMobile, options.responsive));
7611
7850
  return el;
7612
7851
  }
7613
7852
  function prepareCandidate(draft) {
@@ -7636,9 +7875,13 @@ function renderBoundCard(container, schema, options) {
7636
7875
  return selected;
7637
7876
  }
7638
7877
  function createLifecycleActionContext(node) {
7639
- const writeLiveVariable = (key, value) => {
7878
+ const writeLiveVariable = (key, value, silent = false) => {
7640
7879
  if (disposed)
7641
7880
  return;
7881
+ if (silent) {
7882
+ writeDraftVariable(variables, key, cloneJsonData(value));
7883
+ return;
7884
+ }
7642
7885
  updateVariables({ [key]: value });
7643
7886
  };
7644
7887
  return {
@@ -7652,7 +7895,9 @@ function renderBoundCard(container, schema, options) {
7652
7895
  parameterResolver: node.bindingDialect === 'a2ui'
7653
7896
  ? createA2UIParameterResolver(variables, node.dataPath)
7654
7897
  : undefined,
7655
- variableWriter: (key, value) => writeLiveVariable(key, value),
7898
+ variableWriter: (key, value, options) => {
7899
+ writeLiveVariable(key, value, options.silent);
7900
+ },
7656
7901
  botId: options.botId,
7657
7902
  inflightRequests,
7658
7903
  };
@@ -7962,7 +8207,7 @@ function renderStaticCard(container, schema, options) {
7962
8207
  const mediaStates = captureMediaStates(container);
7963
8208
  disposeChartsIn(container); // tear down old chart instances before clearing
7964
8209
  container.innerHTML = '';
7965
- const dom = renderNode(tree, variables, actionContext, isMobile, lifecycleManager, schemaActions);
8210
+ const dom = renderNode(tree, variables, actionContext, isMobile, options.responsive, lifecycleManager, schemaActions);
7966
8211
  container.appendChild(dom);
7967
8212
  restoreScrollPositions(container, scrollPositions);
7968
8213
  restoreMediaStates(container, mediaStates);
@@ -8060,7 +8305,7 @@ function restoreMediaStates(root, states) {
8060
8305
  });
8061
8306
  }
8062
8307
  // ─── Recursive Node Renderer ─────────────────────────────────────
8063
- function renderNode(node, variables, actionContext, isMobile, lifecycleManager, schemaActions) {
8308
+ function renderNode(node, variables, actionContext, isMobile, responsive, lifecycleManager, schemaActions) {
8064
8309
  // Check directives.visible
8065
8310
  if (node.directives?.visible) {
8066
8311
  const visibleExpr = node.directives.visible;
@@ -8092,7 +8337,7 @@ function renderNode(node, variables, actionContext, isMobile, lifecycleManager,
8092
8337
  }
8093
8338
  // Lookup component renderer
8094
8339
  const renderer = componentRenderers[node.type] ?? componentRenderers['_default'];
8095
- const el = renderer(node, resolvedProps, isMobile);
8340
+ const el = renderer(node, resolvedProps, isMobile, responsive);
8096
8341
  // Apply disabled styling & attribute
8097
8342
  if (isDisabled) {
8098
8343
  el.setAttribute('data-disabled', 'true');
@@ -8154,7 +8399,7 @@ function renderNode(node, variables, actionContext, isMobile, lifecycleManager,
8154
8399
  lifecycleManager.mount(node.id, actionContext);
8155
8400
  }
8156
8401
  // Render children — use slot layout if applicable, otherwise flat append
8157
- const renderChild = (child) => renderNode(child, variables, actionContext, isMobile, lifecycleManager, schemaActions);
8402
+ const renderChild = (child) => renderNode(child, variables, actionContext, isMobile, responsive, lifecycleManager, schemaActions);
8158
8403
  // Build children-by-id map for layouts that reference IDs (columns groups, float overlays)
8159
8404
  const childrenMap = {};
8160
8405
  for (const child of node.children) {
@@ -8175,6 +8420,7 @@ function renderNode(node, variables, actionContext, isMobile, lifecycleManager,
8175
8420
  htmlChild.style.setProperty('--card-disabled-color', '#C0C0C0');
8176
8421
  });
8177
8422
  }
8423
+ applyResponsiveStyles(el, createResponsiveContext(isMobile, responsive));
8178
8424
  return el;
8179
8425
  }
8180
8426
  /** Map schema event names → DOM event names */
@@ -8410,7 +8656,7 @@ function renderStreamingCard(container, options = {}) {
8410
8656
  indexes.props.set(node.id, boundNodeFingerprint(node, renderVariables, resolvedProps));
8411
8657
  const isDisabled = computeBoundDisabled(node, renderVariables);
8412
8658
  const renderer = (componentRenderers[node.type] ?? componentRenderers._default);
8413
- const element = renderer(node, resolvedProps, isMobile);
8659
+ const element = renderer(node, resolvedProps, isMobile, options.responsive);
8414
8660
  if (isDisabled) {
8415
8661
  element.setAttribute('data-disabled', 'true');
8416
8662
  element.style.background = '#F5F5F5';
@@ -8461,6 +8707,7 @@ function renderStreamingCard(container, options = {}) {
8461
8707
  element.appendChild(renderChild(child));
8462
8708
  }
8463
8709
  }
8710
+ applyResponsiveStyles(element, createResponsiveContext(isMobile, options.responsive));
8464
8711
  return element;
8465
8712
  }
8466
8713
  /**
@@ -8502,7 +8749,7 @@ function renderStreamingCard(container, options = {}) {
8502
8749
  }
8503
8750
  // Create element
8504
8751
  const renderer = componentRenderers[node.type] ?? componentRenderers['_default'];
8505
- const el = renderer(node, resolvedProps, isMobile);
8752
+ const el = renderer(node, resolvedProps, isMobile, options.responsive);
8506
8753
  // Apply disabled
8507
8754
  if (isDisabled) {
8508
8755
  el.setAttribute('data-disabled', 'true');
@@ -8559,6 +8806,7 @@ function renderStreamingCard(container, options = {}) {
8559
8806
  el.appendChild(renderChild(child));
8560
8807
  }
8561
8808
  }
8809
+ applyResponsiveStyles(el, createResponsiveContext(isMobile, options.responsive));
8562
8810
  return el;
8563
8811
  }
8564
8812
  // ─── Incremental Rendering Helpers ──────────────────────────────
@@ -8689,7 +8937,7 @@ function renderStreamingCard(container, options = {}) {
8689
8937
  if (propsCache.get(id) === key)
8690
8938
  continue;
8691
8939
  if ('updateProps' in el) {
8692
- el.updateProps(resolved, isMobile);
8940
+ el.updateProps(resolved, isMobile, options.responsive);
8693
8941
  propsCache.set(id, key);
8694
8942
  }
8695
8943
  else if (!replaceSubtree(schema, id, el)) {
@@ -9651,6 +9899,21 @@ function renderStreamingCard(container, options = {}) {
9651
9899
  // ─── Engine Event Handlers ──────────────────────────────────────
9652
9900
  const engine = new StreamingEngine({
9653
9901
  onSurfaceCreated(surfaceId, schemaInput) {
9902
+ if (schemaInput) {
9903
+ const nextSchema = engine.getSchema(surfaceId)
9904
+ ?? normalizeSchema(schemaInput);
9905
+ if (requiresBindingMaterialization(nextSchema)) {
9906
+ const baseRevision = boundRevision;
9907
+ const draft = cloneJsonData({
9908
+ ...nextSchema.variables,
9909
+ ...options.variables,
9910
+ });
9911
+ const prepared = prepareBoundFull(nextSchema, cloneJsonData(draft));
9912
+ commitBoundFull(prepared, nextSchema, draft, baseRevision, () => replaceRootContents(nextSchema.variables, draft));
9913
+ currentSurfaceId = surfaceId;
9914
+ return;
9915
+ }
9916
+ }
9654
9917
  teardownBoundLifecycles();
9655
9918
  boundRevision += 1;
9656
9919
  currentSurfaceId = surfaceId;
@@ -9840,7 +10103,7 @@ function renderStreamingCard(container, options = {}) {
9840
10103
  continue;
9841
10104
  const typeChanged = el.getAttribute('data-card-type') !== element.type;
9842
10105
  if (!typeChanged && 'updateProps' in el) {
9843
- el.updateProps(resolved, isMobile);
10106
+ el.updateProps(resolved, isMobile, options.responsive);
9844
10107
  propsCache.set(id, key);
9845
10108
  }
9846
10109
  else if (!typeChanged && isChildrenOnlyChange(propsCache.get(id), key, element)) {
@@ -9950,7 +10213,7 @@ function renderStreamingCard(container, options = {}) {
9950
10213
  const node = indexBoundNodes(nextMaterialized.root).get(elementId);
9951
10214
  if (node && node.id === node.sourceId && 'updateProps' in el) {
9952
10215
  const resolved = resolveBoundNodeProps(node, variables);
9953
- el.updateProps(resolved, isMobile);
10216
+ el.updateProps(resolved, isMobile, options.responsive);
9954
10217
  propsCache.set(elementId, boundNodeFingerprint(node, variables, resolved));
9955
10218
  currentMaterialized = nextMaterialized;
9956
10219
  boundRevision += 1;
@@ -10422,4 +10685,4 @@ class RemoteActionConfigProvider {
10422
10685
  }
10423
10686
  }
10424
10687
 
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 };
10688
+ 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 };