@antglobal/copilot-cards-web 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/index.d.ts +12 -7
- package/dist/index.js +290 -118
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,14 +78,19 @@ Common render options include:
|
|
|
78
78
|
| Option | Purpose |
|
|
79
79
|
| --- | --- |
|
|
80
80
|
| `variables` | Overrides initial schema variables |
|
|
81
|
-
| `botId` |
|
|
82
|
-
| `isMobile` |
|
|
81
|
+
| `botId` | Optionally selects bot-scoped custom action handlers |
|
|
82
|
+
| `isMobile` | Explicitly enables mobile component layout; defaults to `false` |
|
|
83
|
+
| `responsive` | Explicitly enables mobile px-to-rem conversion when configured |
|
|
83
84
|
| `fetch` | Supplies a custom request implementation |
|
|
84
85
|
| `showToast` | Connects toast actions to the host UI |
|
|
85
86
|
| `navigate` | Connects URL actions to host navigation |
|
|
86
87
|
| `emit` | Receives events emitted by a card |
|
|
87
88
|
| `copyText` | Connects copy actions to the host clipboard |
|
|
88
89
|
|
|
90
|
+
The SDK does not infer mobile mode or CSS units from viewport width. Pass only
|
|
91
|
+
`isMobile: true` for mobile layout with px output. To opt into REM conversion,
|
|
92
|
+
also pass `responsive: { mobile: { unit: "rem", rootValue: 100 } }`.
|
|
93
|
+
|
|
89
94
|
### Streaming
|
|
90
95
|
|
|
91
96
|
Use `renderStreamingCard` when the card arrives incrementally from an AI model or server:
|
|
@@ -121,6 +126,8 @@ const bot = new BotSDK({
|
|
|
121
126
|
await bot.renderCard(container, schema);
|
|
122
127
|
```
|
|
123
128
|
|
|
129
|
+
`botId` is optional. Omit it when the application uses only one default action scope; provide it when isolating custom handlers or loading per-bot action configuration.
|
|
130
|
+
|
|
124
131
|
## Built-in components
|
|
125
132
|
|
|
126
133
|
The renderer includes:
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ interface ResponsiveContext {
|
|
|
42
42
|
resolveLength(value: string | number): string;
|
|
43
43
|
convertCSS(value: string): string;
|
|
44
44
|
}
|
|
45
|
+
/** Reusable REM preset; applied only when passed explicitly as `responsive.mobile`. */
|
|
45
46
|
declare const DEFAULT_MOBILE_RESPONSIVE: ResponsiveMobileOptions;
|
|
46
47
|
/** Convert CSS px lengths while preserving strings, comments, and URLs. */
|
|
47
48
|
declare function convertPixelTokens(value: string, rootValue: number): string;
|
|
@@ -62,9 +63,9 @@ declare function createResponsiveContext(isMobile: boolean, responsive?: Respons
|
|
|
62
63
|
*/
|
|
63
64
|
|
|
64
65
|
interface RenderCardOptions extends WebActionContextOptions {
|
|
65
|
-
/**
|
|
66
|
+
/** Enable mobile component layout explicitly (default false). */
|
|
66
67
|
isMobile?: boolean;
|
|
67
|
-
/**
|
|
68
|
+
/** Optional mobile sizing conversion; omitted values preserve px output. */
|
|
68
69
|
responsive?: ResponsiveOptions;
|
|
69
70
|
/** External variables to merge into schema.variables (overrides schema defaults) */
|
|
70
71
|
variables?: Record<string, any>;
|
|
@@ -282,8 +283,8 @@ declare function connectSSE(instance: StreamingCardInstance, options: SSEConnect
|
|
|
282
283
|
*/
|
|
283
284
|
|
|
284
285
|
interface BotSDKOptions {
|
|
285
|
-
/**
|
|
286
|
-
botId
|
|
286
|
+
/** Optional bot ID used to scope custom actions and action configuration */
|
|
287
|
+
botId?: string;
|
|
287
288
|
/** Base URL for business API requests (e.g. 'https://api.example.com') */
|
|
288
289
|
baseUrl?: string;
|
|
289
290
|
/**
|
|
@@ -433,7 +434,7 @@ declare function buildStyleString(styles: Record<string, string | number | undef
|
|
|
433
434
|
* common style helpers. Designed to be used by `renderCard` which
|
|
434
435
|
* passes resolved props via `setData()`.
|
|
435
436
|
*
|
|
436
|
-
* Note: lifecycle management, event binding, and
|
|
437
|
+
* Note: lifecycle management, event binding, and mobile-mode selection
|
|
437
438
|
* are handled externally by `renderCard` / the render pipeline.
|
|
438
439
|
* BaseElement keeps itself lightweight and focused on DOM rendering.
|
|
439
440
|
*/
|
|
@@ -620,6 +621,8 @@ declare class CardButton extends BaseElement {
|
|
|
620
621
|
declare class CardInput extends BaseElement {
|
|
621
622
|
static readonly is = "ai-card-input";
|
|
622
623
|
protected render(): void;
|
|
624
|
+
private getAutoFocusControl;
|
|
625
|
+
private isFirstAvailableAutoFocusInput;
|
|
623
626
|
/** Escape HTML entities for safe insertion. */
|
|
624
627
|
private escapeHtml;
|
|
625
628
|
/** Escape attribute values for safe insertion. */
|
|
@@ -1017,6 +1020,8 @@ type InputAffix = string | InputAffixConfig;
|
|
|
1017
1020
|
interface InputProps {
|
|
1018
1021
|
/** Placeholder text */
|
|
1019
1022
|
placeholder?: string;
|
|
1023
|
+
/** Focus the native input after the component is mounted (default false) */
|
|
1024
|
+
autoFocus?: boolean;
|
|
1020
1025
|
/** HTML input type: text / textarea / password / number etc. */
|
|
1021
1026
|
inputType?: 'text' | 'textarea' | 'password' | (string & {});
|
|
1022
1027
|
/** Label text displayed above the input */
|
|
@@ -1290,7 +1295,7 @@ interface ChoiceListProps {
|
|
|
1290
1295
|
indicatorStyle?: Record<string, any>;
|
|
1291
1296
|
/** Style merged into selected indicators */
|
|
1292
1297
|
selectedIndicatorStyle?: Record<string, any>;
|
|
1293
|
-
/**
|
|
1298
|
+
/** Selected mark override; single mode keeps its radio dot when omitted, while multiple mode defaults to `check_bold` */
|
|
1294
1299
|
checkedIcon?: ChoiceIndicatorIcon;
|
|
1295
1300
|
}
|
|
1296
1301
|
interface ChoiceIndicatorIcon {
|
|
@@ -1318,7 +1323,7 @@ interface ChoiceItemProps {
|
|
|
1318
1323
|
indicatorStyle?: Record<string, any>;
|
|
1319
1324
|
/** Per-item selected indicator style */
|
|
1320
1325
|
selectedIndicatorStyle?: Record<string, any>;
|
|
1321
|
-
/** Per-item
|
|
1326
|
+
/** Per-item selected mark override; also replaces the single-select radio dot */
|
|
1322
1327
|
checkedIcon?: ChoiceIndicatorIcon;
|
|
1323
1328
|
}
|
|
1324
1329
|
interface PasscodeInputProps {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getBuiltinIcon, cloneJsonData, createLifecycleManager, materializeCard,
|
|
1
|
+
import { getBuiltinIcon, cloneJsonData, createLifecycleManager, materializeCard, createA2UIParameterResolver, createExpressionContext, resolveActionRef, replaceRootContents, resolveA2UIDeep, hasExpression, resolveExpression, resolveDeep, resolveExpressionValue, isBoundRenderTreeNode, runActionSteps, normalizeSchema, validateSchema, requiresBindingMaterialization, parseSchema, StreamingParser, StreamingEngine, findAffectedRepeatOwners, bindingTopologyFingerprint, findTemplateRepeatOwners, extractPartialSchema, runActionStep, registry } from '@antglobal/copilot-cards-core';
|
|
2
2
|
export { ActionRegistry, a2uiComponentToElement, a2uiToCommand, convertLegacySchema, createLifecycleManager, hasExpression, isA2UIEnvelope, isLegacySchema, normalizeSchema, parseSchema, registerActionHandler, registry, resolveActionRef, resolveDeep, resolveExpression, resolveExpressionValue, runActionStep, runActionSteps, validateSchema } from '@antglobal/copilot-cards-core';
|
|
3
3
|
import * as echarts from 'echarts/core';
|
|
4
4
|
import { LineChart, BarChart, PieChart, ScatterChart, FunnelChart, HeatmapChart } from 'echarts/charts';
|
|
@@ -96,6 +96,7 @@ function buildStyleString(styles) {
|
|
|
96
96
|
.join(';');
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** Reusable REM preset; applied only when passed explicitly as `responsive.mobile`. */
|
|
99
100
|
const DEFAULT_MOBILE_RESPONSIVE = Object.freeze({
|
|
100
101
|
unit: 'rem',
|
|
101
102
|
rootValue: 100,
|
|
@@ -236,10 +237,10 @@ function convertPixelTokens(value, rootValue) {
|
|
|
236
237
|
return result;
|
|
237
238
|
}
|
|
238
239
|
function createResponsiveContext(isMobile, responsive) {
|
|
239
|
-
const mobile = responsive?.mobile
|
|
240
|
-
const unit = mobile
|
|
241
|
-
const rootValue = mobile.rootValue;
|
|
242
|
-
if (!Number.isFinite(rootValue) || rootValue <= 0) {
|
|
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)) {
|
|
243
244
|
throw new Error('[renderCard] responsive.mobile.rootValue must be a positive number');
|
|
244
245
|
}
|
|
245
246
|
const active = isMobile && unit === 'rem';
|
|
@@ -705,6 +706,7 @@ function buildHeatmapOption(config) {
|
|
|
705
706
|
*/
|
|
706
707
|
// ─── Slot Layout Constants ──────────────────────────────────────
|
|
707
708
|
const SLOT_LAYOUT = {
|
|
709
|
+
FLEX: 'flex',
|
|
708
710
|
COLUMNS: 'columns',
|
|
709
711
|
GRID: 'grid',
|
|
710
712
|
HORIZONTAL_SCROLL: 'horizontalScroll',
|
|
@@ -715,6 +717,30 @@ const SLOT_LAYOUT = {
|
|
|
715
717
|
TABLE: 'table',
|
|
716
718
|
CHART: 'chart',
|
|
717
719
|
};
|
|
720
|
+
// ─── Dispatcher ─────────────────────────────────────────────────
|
|
721
|
+
/**
|
|
722
|
+
* Apply host-level layout styles based on the slot key.
|
|
723
|
+
* Called from renderDefault BEFORE props.style so explicit styles can override.
|
|
724
|
+
* Layouts that require wrapper DOM continue to render in renderSlotLayout.
|
|
725
|
+
*/
|
|
726
|
+
function applySlotBaseStyle(container, props) {
|
|
727
|
+
const flex = props.slots?.[SLOT_LAYOUT.FLEX];
|
|
728
|
+
if (!flex || typeof flex !== 'object')
|
|
729
|
+
return;
|
|
730
|
+
const config = flex.config && typeof flex.config === 'object' ? flex.config : {};
|
|
731
|
+
container.style.display = 'flex';
|
|
732
|
+
container.style.flexDirection = config.direction ?? 'column';
|
|
733
|
+
container.style.alignItems = config.align ?? 'flex-start';
|
|
734
|
+
container.style.justifyContent = config.justify ?? 'flex-start';
|
|
735
|
+
container.style.flexWrap = config.wrap ?? 'nowrap';
|
|
736
|
+
container.style.gap = resolveFlexGap(config.gap);
|
|
737
|
+
}
|
|
738
|
+
function resolveFlexGap(value) {
|
|
739
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
740
|
+
return `${value}px`;
|
|
741
|
+
}
|
|
742
|
+
return typeof value === 'string' && value.length > 0 ? value : '0px';
|
|
743
|
+
}
|
|
718
744
|
/**
|
|
719
745
|
* Render children into a container using the slot layout strategy.
|
|
720
746
|
* Returns true if a special layout was applied (children already appended).
|
|
@@ -852,40 +878,12 @@ function applyGridPlacement(el, child) {
|
|
|
852
878
|
function isCssPlacement(value) {
|
|
853
879
|
return typeof value === 'string' || typeof value === 'number';
|
|
854
880
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
* Optional config:
|
|
862
|
-
* - `arrows` — show prev/next arrow buttons; each is only visible while
|
|
863
|
-
* the track can actually scroll in that direction (default false)
|
|
864
|
-
* - `arrowStyle` — free-form CSS object merged onto both arrow buttons; every
|
|
865
|
-
* default (circle, background, shadow…) can be overridden
|
|
866
|
-
* - `arrowIcon` — replaces the built-in chevron on both buttons: an image URL
|
|
867
|
-
* (http/https/data:image) or a text glyph/emoji. The left
|
|
868
|
-
* button gets a mirrored copy.
|
|
869
|
-
* - `arrowIconLeft` / `arrowIconRight` — per-side icon, same formats, never
|
|
870
|
-
* mirrored; takes precedence over `arrowIcon`
|
|
871
|
-
* - `arrowOffset` — distance of each arrow from its own edge (default '8px');
|
|
872
|
-
* number = px, negative values push the arrows outside /
|
|
873
|
-
* straddling the container edge
|
|
874
|
-
* - `scrollStep` — px scrolled per arrow click (default 80% of the visible width)
|
|
875
|
-
* - `mask` — show translucent edge-fade masks hinting at more content;
|
|
876
|
-
* follows the same can-scroll state as the arrows (default false)
|
|
877
|
-
* - `maskWidth` — width of each edge mask (default '48px')
|
|
878
|
-
* - `maskColor` — base color the mask fades out from; any CSS color incl.
|
|
879
|
-
* rgba for translucency (default '#fff')
|
|
880
|
-
* - `maskOpacity` — opacity of a mask while visible (default 1)
|
|
881
|
-
* - `itemHoverStyle` — CSS object applied to an item on hover and reverted on
|
|
882
|
-
* leave; pure inline-style swap, never triggers a re-render
|
|
883
|
-
* - `autoScroll` — while the pointer hovers the track, auto-advance one item
|
|
884
|
-
* every `autoScrollInterval` ms (default false)
|
|
885
|
-
* - `autoScrollInterval` — ms each item stays before advancing (default 2000)
|
|
886
|
-
* - `autoScrollLoop` — loop back to the first item after the last (default true);
|
|
887
|
-
* when false it stops on the last item until re-hovered
|
|
888
|
-
*/
|
|
881
|
+
function resolveScrollbarMode(value) {
|
|
882
|
+
if (value === 'auto' || value === 'hidden' || value === 'visible') {
|
|
883
|
+
return value;
|
|
884
|
+
}
|
|
885
|
+
return undefined;
|
|
886
|
+
}
|
|
889
887
|
function renderHorizontalScrollSlot(container, children, slotContent, renderChild) {
|
|
890
888
|
const config = slotContent?.config ?? {};
|
|
891
889
|
const gap = config.gap ?? '8px';
|
|
@@ -897,13 +895,16 @@ function renderHorizontalScrollSlot(container, children, slotContent, renderChil
|
|
|
897
895
|
const autoScroll = config.autoScroll === true;
|
|
898
896
|
const autoScrollInterval = typeof config.autoScrollInterval === 'number' ? config.autoScrollInterval : 2000;
|
|
899
897
|
const autoScrollLoop = config.autoScrollLoop !== false;
|
|
898
|
+
const scrollbar = resolveScrollbarMode(config.scrollbar);
|
|
899
|
+
const hidesScrollbar = scrollbar == null || scrollbar === 'hidden';
|
|
900
900
|
const track = document.createElement('div');
|
|
901
901
|
track.style.cssText = [
|
|
902
902
|
'display:flex',
|
|
903
903
|
'overflow-x:auto',
|
|
904
904
|
'overflow-y:hidden',
|
|
905
905
|
'-webkit-overflow-scrolling:touch',
|
|
906
|
-
'scrollbar-width:none',
|
|
906
|
+
hidesScrollbar ? 'scrollbar-width:none' : '',
|
|
907
|
+
scrollbar === 'visible' ? 'scrollbar-width:auto' : '',
|
|
907
908
|
`gap:${gap}`,
|
|
908
909
|
'cursor:grab',
|
|
909
910
|
snap ? 'scroll-snap-type:x mandatory' : '',
|
|
@@ -912,11 +913,8 @@ function renderHorizontalScrollSlot(container, children, slotContent, renderChil
|
|
|
912
913
|
// across variable-driven re-renders (see captureScrollPositions/restoreScrollPositions).
|
|
913
914
|
const cardId = container.getAttribute('data-card-id') ?? 'x';
|
|
914
915
|
track.setAttribute('data-scroll-id', cardId);
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
const trackClass = `card-scroll-${cardId}`;
|
|
918
|
-
styleEl.textContent = `.${trackClass}::-webkit-scrollbar{display:none}`;
|
|
919
|
-
track.classList.add(trackClass);
|
|
916
|
+
if (scrollbar)
|
|
917
|
+
track.dataset.scrollbar = scrollbar;
|
|
920
918
|
for (const child of children) {
|
|
921
919
|
const item = renderChild(child);
|
|
922
920
|
item.style.flexShrink = '0';
|
|
@@ -1040,7 +1038,18 @@ function renderHorizontalScrollSlot(container, children, slotContent, renderChil
|
|
|
1040
1038
|
track.addEventListener('mouseleave', stopAuto);
|
|
1041
1039
|
track.addEventListener('mousedown', stopAuto);
|
|
1042
1040
|
}
|
|
1043
|
-
|
|
1041
|
+
if (hidesScrollbar) {
|
|
1042
|
+
const trackClass = `card-scroll-${cardId}`;
|
|
1043
|
+
track.classList.add(trackClass);
|
|
1044
|
+
const styleEl = document.createElement('style');
|
|
1045
|
+
styleEl.textContent = `.${trackClass}::-webkit-scrollbar{display:none}`;
|
|
1046
|
+
container.appendChild(styleEl);
|
|
1047
|
+
}
|
|
1048
|
+
else if (scrollbar === 'visible') {
|
|
1049
|
+
const styleEl = document.createElement('style');
|
|
1050
|
+
styleEl.textContent = '[data-scroll-id][data-scrollbar="visible"]::-webkit-scrollbar{display:block}';
|
|
1051
|
+
container.appendChild(styleEl);
|
|
1052
|
+
}
|
|
1044
1053
|
// Plain track — no overlay chrome requested
|
|
1045
1054
|
if (!arrows && !mask) {
|
|
1046
1055
|
container.appendChild(track);
|
|
@@ -1113,13 +1122,41 @@ function applyHoverStyle(item, hoverStyle) {
|
|
|
1113
1122
|
const previous = {};
|
|
1114
1123
|
item.addEventListener('mouseenter', () => {
|
|
1115
1124
|
for (const [key, value] of Object.entries(hoverStyle)) {
|
|
1116
|
-
|
|
1117
|
-
|
|
1125
|
+
if (key.startsWith('--')) {
|
|
1126
|
+
const previousValue = item.style.getPropertyValue(key);
|
|
1127
|
+
const previousPriority = item.style.getPropertyPriority(key);
|
|
1128
|
+
previous[key] = {
|
|
1129
|
+
value: previousValue,
|
|
1130
|
+
priority: previousPriority,
|
|
1131
|
+
wasPresent: previousValue !== '' || previousPriority !== '',
|
|
1132
|
+
isCustomProperty: true,
|
|
1133
|
+
};
|
|
1134
|
+
item.style.setProperty(key, String(value));
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
previous[key] = {
|
|
1138
|
+
value: item.style[key],
|
|
1139
|
+
priority: '',
|
|
1140
|
+
wasPresent: true,
|
|
1141
|
+
isCustomProperty: false,
|
|
1142
|
+
};
|
|
1143
|
+
item.style[key] = value;
|
|
1144
|
+
}
|
|
1118
1145
|
}
|
|
1119
1146
|
});
|
|
1120
1147
|
item.addEventListener('mouseleave', () => {
|
|
1121
|
-
for (const [key,
|
|
1122
|
-
|
|
1148
|
+
for (const [key, state] of Object.entries(previous)) {
|
|
1149
|
+
if (state.isCustomProperty) {
|
|
1150
|
+
if (state.wasPresent) {
|
|
1151
|
+
item.style.setProperty(key, state.value, state.priority);
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
item.style.removeProperty(key);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
item.style[key] = state.value;
|
|
1159
|
+
}
|
|
1123
1160
|
}
|
|
1124
1161
|
});
|
|
1125
1162
|
}
|
|
@@ -1647,7 +1684,7 @@ function renderTableSlot(container, slotContent) {
|
|
|
1647
1684
|
* common style helpers. Designed to be used by `renderCard` which
|
|
1648
1685
|
* passes resolved props via `setData()`.
|
|
1649
1686
|
*
|
|
1650
|
-
* Note: lifecycle management, event binding, and
|
|
1687
|
+
* Note: lifecycle management, event binding, and mobile-mode selection
|
|
1651
1688
|
* are handled externally by `renderCard` / the render pipeline.
|
|
1652
1689
|
* BaseElement keeps itself lightweight and focused on DOM rendering.
|
|
1653
1690
|
*/
|
|
@@ -2335,6 +2372,30 @@ function sanitizeIconHtml(icon) {
|
|
|
2335
2372
|
walk(tpl.content);
|
|
2336
2373
|
return tpl.innerHTML;
|
|
2337
2374
|
}
|
|
2375
|
+
function escapeIconAttribute(value) {
|
|
2376
|
+
return value
|
|
2377
|
+
.replace(/&/g, '&')
|
|
2378
|
+
.replace(/"/g, '"')
|
|
2379
|
+
.replace(/</g, '<')
|
|
2380
|
+
.replace(/>/g, '>');
|
|
2381
|
+
}
|
|
2382
|
+
function isDirectImageUrl(icon) {
|
|
2383
|
+
const normalized = icon.replace(/[\s\x00-\x1f]/g, '').toLowerCase();
|
|
2384
|
+
return /^(?:https?:\/\/|\/\/|data:image\/)/.test(normalized)
|
|
2385
|
+
|| /^(?:\.{0,2}\/).+\.(?:avif|gif|jpe?g|png|svg|webp)(?:[?#].*)?$/i.test(icon);
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Button.icon historically accepts sanitized HTML or text. A direct image URL
|
|
2389
|
+
* is a common shorthand, so convert only recognizable image URLs to markup and
|
|
2390
|
+
* leave inline SVG, <img> HTML, emoji, and other text on the existing path.
|
|
2391
|
+
*/
|
|
2392
|
+
function renderButtonIcon(icon) {
|
|
2393
|
+
const value = String(icon ?? '').trim();
|
|
2394
|
+
if (isDirectImageUrl(value)) {
|
|
2395
|
+
return `<img class="card-btn-url-icon" src="${escapeIconAttribute(value)}" alt="" aria-hidden="true" />`;
|
|
2396
|
+
}
|
|
2397
|
+
return sanitizeIconHtml(value);
|
|
2398
|
+
}
|
|
2338
2399
|
class CardButton extends BaseElement {
|
|
2339
2400
|
render() {
|
|
2340
2401
|
if (!this.shadowRoot || !this._node)
|
|
@@ -2489,12 +2550,18 @@ class CardButton extends BaseElement {
|
|
|
2489
2550
|
width: 1em;
|
|
2490
2551
|
height: 1em;
|
|
2491
2552
|
}
|
|
2553
|
+
.card-btn-url-icon {
|
|
2554
|
+
display: block;
|
|
2555
|
+
width: 100%;
|
|
2556
|
+
height: 100%;
|
|
2557
|
+
object-fit: contain;
|
|
2558
|
+
}
|
|
2492
2559
|
</style>
|
|
2493
2560
|
<button
|
|
2494
2561
|
class="card-btn ${variant} ${isMobile ? 'card-mobile' : 'card-desktop'}"
|
|
2495
2562
|
${disabled ? 'disabled' : ''}
|
|
2496
2563
|
style="${inlineStyle}"
|
|
2497
|
-
><span class="card-btn-content">${icon ? `<span class="card-btn-icon">${
|
|
2564
|
+
><span class="card-btn-content">${icon ? `<span class="card-btn-icon">${renderButtonIcon(icon)}</span>` : ''}${displayText}</span></button>
|
|
2498
2565
|
`);
|
|
2499
2566
|
}
|
|
2500
2567
|
}
|
|
@@ -2681,7 +2748,7 @@ class CardInput extends BaseElement {
|
|
|
2681
2748
|
render() {
|
|
2682
2749
|
if (!this.shadowRoot || !this._node)
|
|
2683
2750
|
return;
|
|
2684
|
-
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;
|
|
2751
|
+
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;
|
|
2685
2752
|
const inlineStyle = this.buildInlineStyle(style, isExpressionResultStyle);
|
|
2686
2753
|
const legacyInputStyle = style && typeof style === 'object' && style.resize != null
|
|
2687
2754
|
? { resize: style.resize }
|
|
@@ -2967,6 +3034,14 @@ class CardInput extends BaseElement {
|
|
|
2967
3034
|
detail: { value: inputEl.value },
|
|
2968
3035
|
}));
|
|
2969
3036
|
});
|
|
3037
|
+
if (autoFocus && !disabled) {
|
|
3038
|
+
queueMicrotask(() => {
|
|
3039
|
+
const control = this.getAutoFocusControl();
|
|
3040
|
+
if (control && this.isFirstAvailableAutoFocusInput()) {
|
|
3041
|
+
control.focus();
|
|
3042
|
+
}
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
2970
3045
|
if (isNumber &&
|
|
2971
3046
|
showNumberStepper &&
|
|
2972
3047
|
inputEl instanceof HTMLInputElement) {
|
|
@@ -2995,6 +3070,22 @@ class CardInput extends BaseElement {
|
|
|
2995
3070
|
}
|
|
2996
3071
|
}
|
|
2997
3072
|
// ─── Helpers ──────────────────────────────────────────────────
|
|
3073
|
+
getAutoFocusControl() {
|
|
3074
|
+
if (!this.isConnected
|
|
3075
|
+
|| !this._props.autoFocus
|
|
3076
|
+
|| this.hasAttribute('data-disabled')) {
|
|
3077
|
+
return null;
|
|
3078
|
+
}
|
|
3079
|
+
const control = this.shadowRoot?.querySelector('.card-input');
|
|
3080
|
+
return control && !control.disabled ? control : null;
|
|
3081
|
+
}
|
|
3082
|
+
isFirstAvailableAutoFocusInput() {
|
|
3083
|
+
const root = this.getRootNode();
|
|
3084
|
+
if (!('querySelectorAll' in root))
|
|
3085
|
+
return false;
|
|
3086
|
+
const firstAvailable = Array.from(root.querySelectorAll(CardInput.is)).find(input => input.getAutoFocusControl() !== null);
|
|
3087
|
+
return firstAvailable === this;
|
|
3088
|
+
}
|
|
2998
3089
|
/** Escape HTML entities for safe insertion. */
|
|
2999
3090
|
escapeHtml(str) {
|
|
3000
3091
|
return str
|
|
@@ -4716,7 +4807,7 @@ class CardForm extends BaseElement {
|
|
|
4716
4807
|
this._bindEvents(fields, disabled);
|
|
4717
4808
|
}
|
|
4718
4809
|
_renderField(field, index) {
|
|
4719
|
-
const { name, label, type = 'text', placeholder = '', required = false, rules = [], prefix, suffix,
|
|
4810
|
+
const { name, label, type = 'text', placeholder = '', required = false, rules = [], prefix, suffix, length = 6, } = field;
|
|
4720
4811
|
const val = this._values[name] ?? '';
|
|
4721
4812
|
const error = this._errors[name] || '';
|
|
4722
4813
|
const errorClass = error ? 'error' : '';
|
|
@@ -6988,11 +7079,12 @@ class CardChoiceItem extends BaseElement {
|
|
|
6988
7079
|
...(selected ? selectedIndicatorStyle || {} : {}),
|
|
6989
7080
|
}, isExpressionResultStyle);
|
|
6990
7081
|
const indicatorRole = selectionMode === 'single' ? 'radio' : 'checkbox';
|
|
6991
|
-
const
|
|
7082
|
+
const configuredCheckedIcon = checkedIcon ?? listCheckedIcon;
|
|
7083
|
+
const resolvedCheckedIcon = configuredCheckedIcon ?? {
|
|
6992
7084
|
name: 'check_bold',
|
|
6993
7085
|
size: 12,
|
|
6994
7086
|
};
|
|
6995
|
-
const checkedMark = selectionMode === 'multiple'
|
|
7087
|
+
const checkedMark = selectionMode === 'multiple' || configuredCheckedIcon
|
|
6996
7088
|
? `<span class="choice-icon" aria-hidden="true">${renderIconContent(resolvedCheckedIcon)}</span>`
|
|
6997
7089
|
: '<span class="radio-dot" aria-hidden="true"></span>';
|
|
6998
7090
|
this.setAttribute('data-selected', String(selected));
|
|
@@ -7631,6 +7723,8 @@ function renderDefault(node, props, isMobile, responsive) {
|
|
|
7631
7723
|
div.className = `card-element card-${node.type.toLowerCase()} ${isMobile ? 'card-mobile' : 'card-desktop'}`;
|
|
7632
7724
|
div.setAttribute('data-card-id', node.id);
|
|
7633
7725
|
div.setAttribute('data-card-type', node.type);
|
|
7726
|
+
// Apply slot base styles FIRST (so props.style can override)
|
|
7727
|
+
applySlotBaseStyle(div, props);
|
|
7634
7728
|
if (props.style && typeof props.style === 'object') {
|
|
7635
7729
|
const resolvedStyle = buildStyleString(props.isExpressionResultStyle
|
|
7636
7730
|
? props.style
|
|
@@ -7713,18 +7807,9 @@ function renderBoundCard(container, schema, options) {
|
|
|
7713
7807
|
let currentMaterialized;
|
|
7714
7808
|
let revision = 0;
|
|
7715
7809
|
let disposed = false;
|
|
7716
|
-
|
|
7810
|
+
const isMobile = options.isMobile === true;
|
|
7717
7811
|
let actionQueue = Promise.resolve();
|
|
7718
7812
|
let lifecycleQueue = Promise.resolve();
|
|
7719
|
-
const removeViewportListener = options.isMobile == null
|
|
7720
|
-
? onViewportChange((mobile) => {
|
|
7721
|
-
if (disposed)
|
|
7722
|
-
return;
|
|
7723
|
-
isMobile = mobile;
|
|
7724
|
-
const candidate = prepareCandidate(variables);
|
|
7725
|
-
publishDOM(candidate, false);
|
|
7726
|
-
})
|
|
7727
|
-
: () => { };
|
|
7728
7813
|
function expressionContextFor(node) {
|
|
7729
7814
|
return isBoundRenderTreeNode(node)
|
|
7730
7815
|
? createExpressionContext(node.scope)
|
|
@@ -7961,6 +8046,33 @@ function renderBoundCard(container, schema, options) {
|
|
|
7961
8046
|
writable: true,
|
|
7962
8047
|
});
|
|
7963
8048
|
}
|
|
8049
|
+
function jsonDataEqual(before, after) {
|
|
8050
|
+
if (Object.is(before, after))
|
|
8051
|
+
return true;
|
|
8052
|
+
if (Array.isArray(before) || Array.isArray(after)) {
|
|
8053
|
+
if (!Array.isArray(before) || !Array.isArray(after))
|
|
8054
|
+
return false;
|
|
8055
|
+
return before.length === after.length
|
|
8056
|
+
&& before.every((value, index) => jsonDataEqual(value, after[index]));
|
|
8057
|
+
}
|
|
8058
|
+
if (before === null
|
|
8059
|
+
|| after === null
|
|
8060
|
+
|| typeof before !== 'object'
|
|
8061
|
+
|| typeof after !== 'object') {
|
|
8062
|
+
return false;
|
|
8063
|
+
}
|
|
8064
|
+
const beforeRecord = before;
|
|
8065
|
+
const afterRecord = after;
|
|
8066
|
+
const beforeKeys = Object.keys(beforeRecord);
|
|
8067
|
+
const afterKeys = Object.keys(afterRecord);
|
|
8068
|
+
return beforeKeys.length === afterKeys.length
|
|
8069
|
+
&& beforeKeys.every(key => Object.prototype.hasOwnProperty.call(afterRecord, key)
|
|
8070
|
+
&& jsonDataEqual(beforeRecord[key], afterRecord[key]));
|
|
8071
|
+
}
|
|
8072
|
+
function hasVariableChanges(before, after) {
|
|
8073
|
+
const validatedAfter = cloneJsonData(after);
|
|
8074
|
+
return !jsonDataEqual(before, validatedAfter);
|
|
8075
|
+
}
|
|
7964
8076
|
function createDraftActionContext(node, draft) {
|
|
7965
8077
|
const write = (key, value) => {
|
|
7966
8078
|
writeDraftVariable(draft, key, value);
|
|
@@ -7989,6 +8101,7 @@ function renderBoundCard(container, schema, options) {
|
|
|
7989
8101
|
if (eventDetail !== undefined) {
|
|
7990
8102
|
writeDraftVariable(draft, '_event', cloneJsonData(eventDetail));
|
|
7991
8103
|
}
|
|
8104
|
+
const actionBaseline = cloneJsonData(draft);
|
|
7992
8105
|
const freshMaterialized = materializeCard(schema, draft);
|
|
7993
8106
|
const freshNode = indexNodes(freshMaterialized.root).get(runtimeId);
|
|
7994
8107
|
if (!freshNode) {
|
|
@@ -8002,6 +8115,8 @@ function renderBoundCard(container, schema, options) {
|
|
|
8002
8115
|
return;
|
|
8003
8116
|
await runActionSteps(steps, createDraftActionContext(freshNode, draft));
|
|
8004
8117
|
assertCurrentRevision(baseRevision);
|
|
8118
|
+
if (!hasVariableChanges(actionBaseline, draft))
|
|
8119
|
+
return;
|
|
8005
8120
|
const candidate = prepareCandidate(draft);
|
|
8006
8121
|
commitDraft(draft, candidate, baseRevision);
|
|
8007
8122
|
}
|
|
@@ -8041,7 +8156,6 @@ function renderBoundCard(container, schema, options) {
|
|
|
8041
8156
|
return;
|
|
8042
8157
|
disposed = true;
|
|
8043
8158
|
abortController.abort();
|
|
8044
|
-
removeViewportListener();
|
|
8045
8159
|
disposeChartsIn(container);
|
|
8046
8160
|
container.replaceChildren();
|
|
8047
8161
|
const lifecycleNodes = [...activeLifecycleNodes.entries()];
|
|
@@ -8169,50 +8283,124 @@ function renderStaticCard(container, schema, options) {
|
|
|
8169
8283
|
const tree = parseSchema(schema);
|
|
8170
8284
|
// 3. Reactive variables store (mutable copy, merged with external variables)
|
|
8171
8285
|
let variables = { ...schema.variables, ...options.variables };
|
|
8172
|
-
|
|
8173
|
-
|
|
8286
|
+
let disposed = false;
|
|
8287
|
+
let hostAuthorityEpoch = 0;
|
|
8288
|
+
const lifecycleRecords = new Map();
|
|
8174
8289
|
const abortController = new AbortController();
|
|
8175
8290
|
// Per-instance request dedup map (isolated from other cards on the page).
|
|
8176
8291
|
const inflightRequests = new Map();
|
|
8177
8292
|
function buildActionContext() {
|
|
8178
|
-
|
|
8293
|
+
const baseAuthorityEpoch = hostAuthorityEpoch;
|
|
8294
|
+
let context;
|
|
8295
|
+
const writeVariable = (key, value, { silent }) => {
|
|
8296
|
+
let actionVariables = context.variables;
|
|
8297
|
+
if (disposed || baseAuthorityEpoch !== hostAuthorityEpoch) {
|
|
8298
|
+
if (actionVariables === variables) {
|
|
8299
|
+
actionVariables = { ...actionVariables };
|
|
8300
|
+
context.variables = actionVariables;
|
|
8301
|
+
}
|
|
8302
|
+
actionVariables[key] = value;
|
|
8303
|
+
return;
|
|
8304
|
+
}
|
|
8305
|
+
actionVariables[key] = value;
|
|
8306
|
+
if (!silent)
|
|
8307
|
+
rerender();
|
|
8308
|
+
};
|
|
8309
|
+
context = {
|
|
8179
8310
|
...createWebActionContext({
|
|
8180
8311
|
...options,
|
|
8181
8312
|
setVariable: (key, value) => {
|
|
8182
|
-
|
|
8183
|
-
// Trigger re-render after variable change
|
|
8184
|
-
rerender();
|
|
8313
|
+
writeVariable(key, value, { silent: false});
|
|
8185
8314
|
},
|
|
8186
8315
|
abortSignal: abortController.signal,
|
|
8187
8316
|
}),
|
|
8188
8317
|
variables,
|
|
8318
|
+
variableWriter: writeVariable,
|
|
8189
8319
|
botId: options.botId,
|
|
8190
8320
|
inflightRequests,
|
|
8191
8321
|
};
|
|
8322
|
+
return context;
|
|
8192
8323
|
}
|
|
8193
8324
|
let actionContext = buildActionContext();
|
|
8194
8325
|
// 5. Responsive
|
|
8195
|
-
|
|
8196
|
-
const removeViewportListener = options.isMobile == null
|
|
8197
|
-
? onViewportChange((mobile) => {
|
|
8198
|
-
isMobile = mobile;
|
|
8199
|
-
rerender();
|
|
8200
|
-
})
|
|
8201
|
-
: () => { };
|
|
8326
|
+
const isMobile = options.isMobile === true;
|
|
8202
8327
|
// 6. Schema-level action definitions (for string references in events)
|
|
8203
8328
|
const schemaActions = schema.actions ?? {};
|
|
8329
|
+
function destroyLifecycle(record) {
|
|
8330
|
+
if (record.state !== 'mounted')
|
|
8331
|
+
return;
|
|
8332
|
+
record.state = 'destroying';
|
|
8333
|
+
void runActionSteps(record.lifecycle.onDestroy ?? [], buildActionContext())
|
|
8334
|
+
.catch((error) => {
|
|
8335
|
+
console.error('[renderCard] Static lifecycle destroy failed', error);
|
|
8336
|
+
})
|
|
8337
|
+
.finally(() => {
|
|
8338
|
+
record.state = 'destroyed';
|
|
8339
|
+
});
|
|
8340
|
+
}
|
|
8341
|
+
function startLifecycleMount(id, lifecycle) {
|
|
8342
|
+
if (disposed || lifecycleRecords.has(id))
|
|
8343
|
+
return;
|
|
8344
|
+
const record = { lifecycle, state: 'mounting' };
|
|
8345
|
+
lifecycleRecords.set(id, record);
|
|
8346
|
+
void runActionSteps(lifecycle.onMount ?? [], buildActionContext())
|
|
8347
|
+
.then(() => {
|
|
8348
|
+
record.state = 'mounted';
|
|
8349
|
+
if (disposed)
|
|
8350
|
+
destroyLifecycle(record);
|
|
8351
|
+
})
|
|
8352
|
+
.catch((error) => {
|
|
8353
|
+
console.error('[renderCard] Static lifecycle mount failed', error);
|
|
8354
|
+
if (disposed) {
|
|
8355
|
+
record.state = 'destroyed';
|
|
8356
|
+
}
|
|
8357
|
+
else if (lifecycleRecords.get(id) === record) {
|
|
8358
|
+
lifecycleRecords.delete(id);
|
|
8359
|
+
}
|
|
8360
|
+
});
|
|
8361
|
+
}
|
|
8204
8362
|
// 7. Render function
|
|
8363
|
+
let renderGeneration = 0;
|
|
8205
8364
|
function render() {
|
|
8365
|
+
if (disposed)
|
|
8366
|
+
return;
|
|
8367
|
+
const generation = ++renderGeneration;
|
|
8368
|
+
const collectedLifecycles = [];
|
|
8206
8369
|
const scrollPositions = captureScrollPositions(container);
|
|
8207
8370
|
const mediaStates = captureMediaStates(container);
|
|
8208
8371
|
disposeChartsIn(container); // tear down old chart instances before clearing
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8372
|
+
const dom = renderNode(tree, variables, actionContext, isMobile, options.responsive, schemaActions, (key, value, sourceVariables) => {
|
|
8373
|
+
if (disposed || sourceVariables !== variables)
|
|
8374
|
+
return;
|
|
8375
|
+
variables[key] = value;
|
|
8376
|
+
hostAuthorityEpoch += 1;
|
|
8377
|
+
}, (steps, detail) => {
|
|
8378
|
+
if (disposed)
|
|
8379
|
+
return;
|
|
8380
|
+
if (detail != null)
|
|
8381
|
+
variables._event = detail;
|
|
8382
|
+
void runActionSteps(steps, buildActionContext()).catch((error) => {
|
|
8383
|
+
console.error('[renderCard] Static action failed', error);
|
|
8384
|
+
});
|
|
8385
|
+
}, (id, lifecycle) => {
|
|
8386
|
+
collectedLifecycles.push({ id, lifecycle });
|
|
8387
|
+
});
|
|
8388
|
+
if (disposed || generation !== renderGeneration)
|
|
8389
|
+
return;
|
|
8390
|
+
container.replaceChildren(dom);
|
|
8212
8391
|
restoreScrollPositions(container, scrollPositions);
|
|
8213
8392
|
restoreMediaStates(container, mediaStates);
|
|
8393
|
+
if (disposed || generation !== renderGeneration)
|
|
8394
|
+
return;
|
|
8395
|
+
for (const { id, lifecycle } of collectedLifecycles) {
|
|
8396
|
+
if (disposed || generation !== renderGeneration)
|
|
8397
|
+
break;
|
|
8398
|
+
startLifecycleMount(id, lifecycle);
|
|
8399
|
+
}
|
|
8214
8400
|
}
|
|
8215
8401
|
function rerender() {
|
|
8402
|
+
if (disposed)
|
|
8403
|
+
return;
|
|
8216
8404
|
actionContext = buildActionContext();
|
|
8217
8405
|
render();
|
|
8218
8406
|
}
|
|
@@ -8221,13 +8409,20 @@ function renderStaticCard(container, schema, options) {
|
|
|
8221
8409
|
// 8. Return instance handle
|
|
8222
8410
|
return {
|
|
8223
8411
|
dispose() {
|
|
8412
|
+
if (disposed)
|
|
8413
|
+
return;
|
|
8414
|
+
disposed = true;
|
|
8415
|
+
hostAuthorityEpoch += 1;
|
|
8224
8416
|
abortController.abort();
|
|
8225
|
-
removeViewportListener();
|
|
8226
|
-
lifecycleManager.dispose(actionContext);
|
|
8227
8417
|
disposeChartsIn(container);
|
|
8228
|
-
container.
|
|
8418
|
+
container.replaceChildren();
|
|
8419
|
+
for (const record of lifecycleRecords.values())
|
|
8420
|
+
destroyLifecycle(record);
|
|
8229
8421
|
},
|
|
8230
8422
|
updateVariables(newVars) {
|
|
8423
|
+
if (disposed)
|
|
8424
|
+
return;
|
|
8425
|
+
hostAuthorityEpoch += 1;
|
|
8231
8426
|
variables = { ...variables, ...newVars };
|
|
8232
8427
|
rerender();
|
|
8233
8428
|
},
|
|
@@ -8305,7 +8500,7 @@ function restoreMediaStates(root, states) {
|
|
|
8305
8500
|
});
|
|
8306
8501
|
}
|
|
8307
8502
|
// ─── Recursive Node Renderer ─────────────────────────────────────
|
|
8308
|
-
function renderNode(node, variables, actionContext, isMobile, responsive,
|
|
8503
|
+
function renderNode(node, variables, actionContext, isMobile, responsive, schemaActions, writeInputVariable, runEventActions, collectLifecycle) {
|
|
8309
8504
|
// Check directives.visible
|
|
8310
8505
|
if (node.directives?.visible) {
|
|
8311
8506
|
const visibleExpr = node.directives.visible;
|
|
@@ -8363,7 +8558,7 @@ function renderNode(node, variables, actionContext, isMobile, responsive, lifecy
|
|
|
8363
8558
|
?? e.target?.value;
|
|
8364
8559
|
if (value !== undefined) {
|
|
8365
8560
|
// Write silently — do NOT trigger rerender (avoids losing focus)
|
|
8366
|
-
|
|
8561
|
+
writeInputVariable(variableKey, value, variables);
|
|
8367
8562
|
}
|
|
8368
8563
|
}));
|
|
8369
8564
|
}
|
|
@@ -8385,21 +8580,17 @@ function renderNode(node, variables, actionContext, isMobile, responsive, lifecy
|
|
|
8385
8580
|
&& VALUE_CONTROL_TYPES.has(node.type));
|
|
8386
8581
|
if (ownsValueEvent && e.target !== el)
|
|
8387
8582
|
return;
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
variables._event = e.detail;
|
|
8391
|
-
}
|
|
8392
|
-
runActionSteps(resolvedSteps, actionContext);
|
|
8583
|
+
const detail = e instanceof CustomEvent ? e.detail : undefined;
|
|
8584
|
+
runEventActions(resolvedSteps, detail);
|
|
8393
8585
|
}));
|
|
8394
8586
|
}
|
|
8395
8587
|
}
|
|
8396
8588
|
// Register lifecycle
|
|
8397
8589
|
if (node.lifecycle) {
|
|
8398
|
-
|
|
8399
|
-
lifecycleManager.mount(node.id, actionContext);
|
|
8590
|
+
collectLifecycle(node.id, node.lifecycle);
|
|
8400
8591
|
}
|
|
8401
8592
|
// Render children — use slot layout if applicable, otherwise flat append
|
|
8402
|
-
const renderChild = (child) => renderNode(child, variables, actionContext, isMobile, responsive,
|
|
8593
|
+
const renderChild = (child) => renderNode(child, variables, actionContext, isMobile, responsive, schemaActions, writeInputVariable, runEventActions, collectLifecycle);
|
|
8403
8594
|
// Build children-by-id map for layouts that reference IDs (columns groups, float overlays)
|
|
8404
8595
|
const childrenMap = {};
|
|
8405
8596
|
for (const child of node.children) {
|
|
@@ -8495,7 +8686,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8495
8686
|
let partialVariablesSent = false;
|
|
8496
8687
|
let partialFinalized = false;
|
|
8497
8688
|
let variables = { ...options.variables };
|
|
8498
|
-
|
|
8689
|
+
const isMobile = options.isMobile === true;
|
|
8499
8690
|
let currentSchema = null;
|
|
8500
8691
|
let currentSurfaceId = null;
|
|
8501
8692
|
let currentMaterialized = null;
|
|
@@ -8507,16 +8698,6 @@ function renderStreamingCard(container, options = {}) {
|
|
|
8507
8698
|
const activeBoundLifecycles = new Map();
|
|
8508
8699
|
const mountedBoundLifecycles = new Map();
|
|
8509
8700
|
const boundLifecycleGenerations = new Map();
|
|
8510
|
-
// Responsive viewport detection
|
|
8511
|
-
const removeViewportListener = options.isMobile == null
|
|
8512
|
-
? onViewportChange((mobile) => {
|
|
8513
|
-
isMobile = mobile;
|
|
8514
|
-
// Re-render all elements with new mobile state if schema exists
|
|
8515
|
-
if (currentSchema) {
|
|
8516
|
-
rerenderAll();
|
|
8517
|
-
}
|
|
8518
|
-
})
|
|
8519
|
-
: () => { };
|
|
8520
8701
|
// ─── Action Context ─────────────────────────────────────────────
|
|
8521
8702
|
function buildActionContext() {
|
|
8522
8703
|
return {
|
|
@@ -9888,14 +10069,6 @@ function renderStreamingCard(container, options = {}) {
|
|
|
9888
10069
|
console.warn('[renderStreamingCard] Deferred render (incomplete schema):', error);
|
|
9889
10070
|
}
|
|
9890
10071
|
}
|
|
9891
|
-
/**
|
|
9892
|
-
* Re-render all elements (viewport changes — isMobile affects every renderer's
|
|
9893
|
-
* output, so the incremental diff cache cannot be reused here).
|
|
9894
|
-
*/
|
|
9895
|
-
function rerenderAll() {
|
|
9896
|
-
actionContext = buildActionContext();
|
|
9897
|
-
safeRenderFull();
|
|
9898
|
-
}
|
|
9899
10072
|
// ─── Engine Event Handlers ──────────────────────────────────────
|
|
9900
10073
|
const engine = new StreamingEngine({
|
|
9901
10074
|
onSurfaceCreated(surfaceId, schemaInput) {
|
|
@@ -10240,7 +10413,7 @@ function renderStreamingCard(container, options = {}) {
|
|
|
10240
10413
|
}
|
|
10241
10414
|
}
|
|
10242
10415
|
},
|
|
10243
|
-
onSurfaceDeleted(
|
|
10416
|
+
onSurfaceDeleted(_surfaceId) {
|
|
10244
10417
|
disposeChartsIn(container); // release old ECharts instances before clearing
|
|
10245
10418
|
container.innerHTML = '';
|
|
10246
10419
|
elementMap.clear();
|
|
@@ -10335,7 +10508,6 @@ function renderStreamingCard(container, options = {}) {
|
|
|
10335
10508
|
disposed = true;
|
|
10336
10509
|
boundRevision += 1;
|
|
10337
10510
|
abortController.abort();
|
|
10338
|
-
removeViewportListener();
|
|
10339
10511
|
teardownBoundLifecycles();
|
|
10340
10512
|
lifecycleManager.dispose(actionContext);
|
|
10341
10513
|
disposeChartsIn(container); // release ECharts instances before clearing
|
|
@@ -10504,7 +10676,7 @@ class BotSDK {
|
|
|
10504
10676
|
this._instances = [];
|
|
10505
10677
|
/** Declarative action chains loaded from actionProvider */
|
|
10506
10678
|
this._actionChains = new Map();
|
|
10507
|
-
this.botId = options.botId;
|
|
10679
|
+
this.botId = options.botId ?? '';
|
|
10508
10680
|
this.baseUrl = options.baseUrl ?? '';
|
|
10509
10681
|
// Source B: Batch-register action handler functions (sync, immediate)
|
|
10510
10682
|
if (options.onAction) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antglobal/copilot-cards-web",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Web Component renderer for copilot bot card SDK — PC + Mobile + WebView unified rendering via Custom Elements",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@antglobal/copilot-cards-core": "^1.0.
|
|
31
|
+
"@antglobal/copilot-cards-core": "^1.0.6",
|
|
32
32
|
"echarts": "^5.6.0",
|
|
33
33
|
"marked": "^18.0.5",
|
|
34
34
|
"tslib": "^2.8.1",
|