@antglobal/copilot-cards-web 1.0.5 → 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/dist/index.d.ts +2 -2
- package/dist/index.js +252 -69
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1295,7 +1295,7 @@ interface ChoiceListProps {
|
|
|
1295
1295
|
indicatorStyle?: Record<string, any>;
|
|
1296
1296
|
/** Style merged into selected indicators */
|
|
1297
1297
|
selectedIndicatorStyle?: Record<string, any>;
|
|
1298
|
-
/**
|
|
1298
|
+
/** Selected mark override; single mode keeps its radio dot when omitted, while multiple mode defaults to `check_bold` */
|
|
1299
1299
|
checkedIcon?: ChoiceIndicatorIcon;
|
|
1300
1300
|
}
|
|
1301
1301
|
interface ChoiceIndicatorIcon {
|
|
@@ -1323,7 +1323,7 @@ interface ChoiceItemProps {
|
|
|
1323
1323
|
indicatorStyle?: Record<string, any>;
|
|
1324
1324
|
/** Per-item selected indicator style */
|
|
1325
1325
|
selectedIndicatorStyle?: Record<string, any>;
|
|
1326
|
-
/** Per-item
|
|
1326
|
+
/** Per-item selected mark override; also replaces the single-select radio dot */
|
|
1327
1327
|
checkedIcon?: ChoiceIndicatorIcon;
|
|
1328
1328
|
}
|
|
1329
1329
|
interface PasscodeInputProps {
|
package/dist/index.js
CHANGED
|
@@ -706,6 +706,7 @@ function buildHeatmapOption(config) {
|
|
|
706
706
|
*/
|
|
707
707
|
// ─── Slot Layout Constants ──────────────────────────────────────
|
|
708
708
|
const SLOT_LAYOUT = {
|
|
709
|
+
FLEX: 'flex',
|
|
709
710
|
COLUMNS: 'columns',
|
|
710
711
|
GRID: 'grid',
|
|
711
712
|
HORIZONTAL_SCROLL: 'horizontalScroll',
|
|
@@ -716,6 +717,30 @@ const SLOT_LAYOUT = {
|
|
|
716
717
|
TABLE: 'table',
|
|
717
718
|
CHART: 'chart',
|
|
718
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
|
+
}
|
|
719
744
|
/**
|
|
720
745
|
* Render children into a container using the slot layout strategy.
|
|
721
746
|
* Returns true if a special layout was applied (children already appended).
|
|
@@ -853,40 +878,12 @@ function applyGridPlacement(el, child) {
|
|
|
853
878
|
function isCssPlacement(value) {
|
|
854
879
|
return typeof value === 'string' || typeof value === 'number';
|
|
855
880
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
* Optional config:
|
|
863
|
-
* - `arrows` — show prev/next arrow buttons; each is only visible while
|
|
864
|
-
* the track can actually scroll in that direction (default false)
|
|
865
|
-
* - `arrowStyle` — free-form CSS object merged onto both arrow buttons; every
|
|
866
|
-
* default (circle, background, shadow…) can be overridden
|
|
867
|
-
* - `arrowIcon` — replaces the built-in chevron on both buttons: an image URL
|
|
868
|
-
* (http/https/data:image) or a text glyph/emoji. The left
|
|
869
|
-
* button gets a mirrored copy.
|
|
870
|
-
* - `arrowIconLeft` / `arrowIconRight` — per-side icon, same formats, never
|
|
871
|
-
* mirrored; takes precedence over `arrowIcon`
|
|
872
|
-
* - `arrowOffset` — distance of each arrow from its own edge (default '8px');
|
|
873
|
-
* number = px, negative values push the arrows outside /
|
|
874
|
-
* straddling the container edge
|
|
875
|
-
* - `scrollStep` — px scrolled per arrow click (default 80% of the visible width)
|
|
876
|
-
* - `mask` — show translucent edge-fade masks hinting at more content;
|
|
877
|
-
* follows the same can-scroll state as the arrows (default false)
|
|
878
|
-
* - `maskWidth` — width of each edge mask (default '48px')
|
|
879
|
-
* - `maskColor` — base color the mask fades out from; any CSS color incl.
|
|
880
|
-
* rgba for translucency (default '#fff')
|
|
881
|
-
* - `maskOpacity` — opacity of a mask while visible (default 1)
|
|
882
|
-
* - `itemHoverStyle` — CSS object applied to an item on hover and reverted on
|
|
883
|
-
* leave; pure inline-style swap, never triggers a re-render
|
|
884
|
-
* - `autoScroll` — while the pointer hovers the track, auto-advance one item
|
|
885
|
-
* every `autoScrollInterval` ms (default false)
|
|
886
|
-
* - `autoScrollInterval` — ms each item stays before advancing (default 2000)
|
|
887
|
-
* - `autoScrollLoop` — loop back to the first item after the last (default true);
|
|
888
|
-
* when false it stops on the last item until re-hovered
|
|
889
|
-
*/
|
|
881
|
+
function resolveScrollbarMode(value) {
|
|
882
|
+
if (value === 'auto' || value === 'hidden' || value === 'visible') {
|
|
883
|
+
return value;
|
|
884
|
+
}
|
|
885
|
+
return undefined;
|
|
886
|
+
}
|
|
890
887
|
function renderHorizontalScrollSlot(container, children, slotContent, renderChild) {
|
|
891
888
|
const config = slotContent?.config ?? {};
|
|
892
889
|
const gap = config.gap ?? '8px';
|
|
@@ -898,13 +895,16 @@ function renderHorizontalScrollSlot(container, children, slotContent, renderChil
|
|
|
898
895
|
const autoScroll = config.autoScroll === true;
|
|
899
896
|
const autoScrollInterval = typeof config.autoScrollInterval === 'number' ? config.autoScrollInterval : 2000;
|
|
900
897
|
const autoScrollLoop = config.autoScrollLoop !== false;
|
|
898
|
+
const scrollbar = resolveScrollbarMode(config.scrollbar);
|
|
899
|
+
const hidesScrollbar = scrollbar == null || scrollbar === 'hidden';
|
|
901
900
|
const track = document.createElement('div');
|
|
902
901
|
track.style.cssText = [
|
|
903
902
|
'display:flex',
|
|
904
903
|
'overflow-x:auto',
|
|
905
904
|
'overflow-y:hidden',
|
|
906
905
|
'-webkit-overflow-scrolling:touch',
|
|
907
|
-
'scrollbar-width:none',
|
|
906
|
+
hidesScrollbar ? 'scrollbar-width:none' : '',
|
|
907
|
+
scrollbar === 'visible' ? 'scrollbar-width:auto' : '',
|
|
908
908
|
`gap:${gap}`,
|
|
909
909
|
'cursor:grab',
|
|
910
910
|
snap ? 'scroll-snap-type:x mandatory' : '',
|
|
@@ -913,11 +913,8 @@ function renderHorizontalScrollSlot(container, children, slotContent, renderChil
|
|
|
913
913
|
// across variable-driven re-renders (see captureScrollPositions/restoreScrollPositions).
|
|
914
914
|
const cardId = container.getAttribute('data-card-id') ?? 'x';
|
|
915
915
|
track.setAttribute('data-scroll-id', cardId);
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
const trackClass = `card-scroll-${cardId}`;
|
|
919
|
-
styleEl.textContent = `.${trackClass}::-webkit-scrollbar{display:none}`;
|
|
920
|
-
track.classList.add(trackClass);
|
|
916
|
+
if (scrollbar)
|
|
917
|
+
track.dataset.scrollbar = scrollbar;
|
|
921
918
|
for (const child of children) {
|
|
922
919
|
const item = renderChild(child);
|
|
923
920
|
item.style.flexShrink = '0';
|
|
@@ -1041,7 +1038,18 @@ function renderHorizontalScrollSlot(container, children, slotContent, renderChil
|
|
|
1041
1038
|
track.addEventListener('mouseleave', stopAuto);
|
|
1042
1039
|
track.addEventListener('mousedown', stopAuto);
|
|
1043
1040
|
}
|
|
1044
|
-
|
|
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
|
+
}
|
|
1045
1053
|
// Plain track — no overlay chrome requested
|
|
1046
1054
|
if (!arrows && !mask) {
|
|
1047
1055
|
container.appendChild(track);
|
|
@@ -1114,13 +1122,41 @@ function applyHoverStyle(item, hoverStyle) {
|
|
|
1114
1122
|
const previous = {};
|
|
1115
1123
|
item.addEventListener('mouseenter', () => {
|
|
1116
1124
|
for (const [key, value] of Object.entries(hoverStyle)) {
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
+
}
|
|
1119
1145
|
}
|
|
1120
1146
|
});
|
|
1121
1147
|
item.addEventListener('mouseleave', () => {
|
|
1122
|
-
for (const [key,
|
|
1123
|
-
|
|
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
|
+
}
|
|
1124
1160
|
}
|
|
1125
1161
|
});
|
|
1126
1162
|
}
|
|
@@ -2336,6 +2372,30 @@ function sanitizeIconHtml(icon) {
|
|
|
2336
2372
|
walk(tpl.content);
|
|
2337
2373
|
return tpl.innerHTML;
|
|
2338
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
|
+
}
|
|
2339
2399
|
class CardButton extends BaseElement {
|
|
2340
2400
|
render() {
|
|
2341
2401
|
if (!this.shadowRoot || !this._node)
|
|
@@ -2490,12 +2550,18 @@ class CardButton extends BaseElement {
|
|
|
2490
2550
|
width: 1em;
|
|
2491
2551
|
height: 1em;
|
|
2492
2552
|
}
|
|
2553
|
+
.card-btn-url-icon {
|
|
2554
|
+
display: block;
|
|
2555
|
+
width: 100%;
|
|
2556
|
+
height: 100%;
|
|
2557
|
+
object-fit: contain;
|
|
2558
|
+
}
|
|
2493
2559
|
</style>
|
|
2494
2560
|
<button
|
|
2495
2561
|
class="card-btn ${variant} ${isMobile ? 'card-mobile' : 'card-desktop'}"
|
|
2496
2562
|
${disabled ? 'disabled' : ''}
|
|
2497
2563
|
style="${inlineStyle}"
|
|
2498
|
-
><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>
|
|
2499
2565
|
`);
|
|
2500
2566
|
}
|
|
2501
2567
|
}
|
|
@@ -7013,11 +7079,12 @@ class CardChoiceItem extends BaseElement {
|
|
|
7013
7079
|
...(selected ? selectedIndicatorStyle || {} : {}),
|
|
7014
7080
|
}, isExpressionResultStyle);
|
|
7015
7081
|
const indicatorRole = selectionMode === 'single' ? 'radio' : 'checkbox';
|
|
7016
|
-
const
|
|
7082
|
+
const configuredCheckedIcon = checkedIcon ?? listCheckedIcon;
|
|
7083
|
+
const resolvedCheckedIcon = configuredCheckedIcon ?? {
|
|
7017
7084
|
name: 'check_bold',
|
|
7018
7085
|
size: 12,
|
|
7019
7086
|
};
|
|
7020
|
-
const checkedMark = selectionMode === 'multiple'
|
|
7087
|
+
const checkedMark = selectionMode === 'multiple' || configuredCheckedIcon
|
|
7021
7088
|
? `<span class="choice-icon" aria-hidden="true">${renderIconContent(resolvedCheckedIcon)}</span>`
|
|
7022
7089
|
: '<span class="radio-dot" aria-hidden="true"></span>';
|
|
7023
7090
|
this.setAttribute('data-selected', String(selected));
|
|
@@ -7656,6 +7723,8 @@ function renderDefault(node, props, isMobile, responsive) {
|
|
|
7656
7723
|
div.className = `card-element card-${node.type.toLowerCase()} ${isMobile ? 'card-mobile' : 'card-desktop'}`;
|
|
7657
7724
|
div.setAttribute('data-card-id', node.id);
|
|
7658
7725
|
div.setAttribute('data-card-type', node.type);
|
|
7726
|
+
// Apply slot base styles FIRST (so props.style can override)
|
|
7727
|
+
applySlotBaseStyle(div, props);
|
|
7659
7728
|
if (props.style && typeof props.style === 'object') {
|
|
7660
7729
|
const resolvedStyle = buildStyleString(props.isExpressionResultStyle
|
|
7661
7730
|
? props.style
|
|
@@ -7977,6 +8046,33 @@ function renderBoundCard(container, schema, options) {
|
|
|
7977
8046
|
writable: true,
|
|
7978
8047
|
});
|
|
7979
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
|
+
}
|
|
7980
8076
|
function createDraftActionContext(node, draft) {
|
|
7981
8077
|
const write = (key, value) => {
|
|
7982
8078
|
writeDraftVariable(draft, key, value);
|
|
@@ -8005,6 +8101,7 @@ function renderBoundCard(container, schema, options) {
|
|
|
8005
8101
|
if (eventDetail !== undefined) {
|
|
8006
8102
|
writeDraftVariable(draft, '_event', cloneJsonData(eventDetail));
|
|
8007
8103
|
}
|
|
8104
|
+
const actionBaseline = cloneJsonData(draft);
|
|
8008
8105
|
const freshMaterialized = materializeCard(schema, draft);
|
|
8009
8106
|
const freshNode = indexNodes(freshMaterialized.root).get(runtimeId);
|
|
8010
8107
|
if (!freshNode) {
|
|
@@ -8018,6 +8115,8 @@ function renderBoundCard(container, schema, options) {
|
|
|
8018
8115
|
return;
|
|
8019
8116
|
await runActionSteps(steps, createDraftActionContext(freshNode, draft));
|
|
8020
8117
|
assertCurrentRevision(baseRevision);
|
|
8118
|
+
if (!hasVariableChanges(actionBaseline, draft))
|
|
8119
|
+
return;
|
|
8021
8120
|
const candidate = prepareCandidate(draft);
|
|
8022
8121
|
commitDraft(draft, candidate, baseRevision);
|
|
8023
8122
|
}
|
|
@@ -8184,44 +8283,124 @@ function renderStaticCard(container, schema, options) {
|
|
|
8184
8283
|
const tree = parseSchema(schema);
|
|
8185
8284
|
// 3. Reactive variables store (mutable copy, merged with external variables)
|
|
8186
8285
|
let variables = { ...schema.variables, ...options.variables };
|
|
8187
|
-
|
|
8188
|
-
|
|
8286
|
+
let disposed = false;
|
|
8287
|
+
let hostAuthorityEpoch = 0;
|
|
8288
|
+
const lifecycleRecords = new Map();
|
|
8189
8289
|
const abortController = new AbortController();
|
|
8190
8290
|
// Per-instance request dedup map (isolated from other cards on the page).
|
|
8191
8291
|
const inflightRequests = new Map();
|
|
8192
8292
|
function buildActionContext() {
|
|
8193
|
-
|
|
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 = {
|
|
8194
8310
|
...createWebActionContext({
|
|
8195
8311
|
...options,
|
|
8196
8312
|
setVariable: (key, value) => {
|
|
8197
|
-
|
|
8198
|
-
// Trigger re-render after variable change
|
|
8199
|
-
rerender();
|
|
8313
|
+
writeVariable(key, value, { silent: false});
|
|
8200
8314
|
},
|
|
8201
8315
|
abortSignal: abortController.signal,
|
|
8202
8316
|
}),
|
|
8203
8317
|
variables,
|
|
8318
|
+
variableWriter: writeVariable,
|
|
8204
8319
|
botId: options.botId,
|
|
8205
8320
|
inflightRequests,
|
|
8206
8321
|
};
|
|
8322
|
+
return context;
|
|
8207
8323
|
}
|
|
8208
8324
|
let actionContext = buildActionContext();
|
|
8209
8325
|
// 5. Responsive
|
|
8210
8326
|
const isMobile = options.isMobile === true;
|
|
8211
8327
|
// 6. Schema-level action definitions (for string references in events)
|
|
8212
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
|
+
}
|
|
8213
8362
|
// 7. Render function
|
|
8363
|
+
let renderGeneration = 0;
|
|
8214
8364
|
function render() {
|
|
8365
|
+
if (disposed)
|
|
8366
|
+
return;
|
|
8367
|
+
const generation = ++renderGeneration;
|
|
8368
|
+
const collectedLifecycles = [];
|
|
8215
8369
|
const scrollPositions = captureScrollPositions(container);
|
|
8216
8370
|
const mediaStates = captureMediaStates(container);
|
|
8217
8371
|
disposeChartsIn(container); // tear down old chart instances before clearing
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
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);
|
|
8221
8391
|
restoreScrollPositions(container, scrollPositions);
|
|
8222
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
|
+
}
|
|
8223
8400
|
}
|
|
8224
8401
|
function rerender() {
|
|
8402
|
+
if (disposed)
|
|
8403
|
+
return;
|
|
8225
8404
|
actionContext = buildActionContext();
|
|
8226
8405
|
render();
|
|
8227
8406
|
}
|
|
@@ -8230,12 +8409,20 @@ function renderStaticCard(container, schema, options) {
|
|
|
8230
8409
|
// 8. Return instance handle
|
|
8231
8410
|
return {
|
|
8232
8411
|
dispose() {
|
|
8412
|
+
if (disposed)
|
|
8413
|
+
return;
|
|
8414
|
+
disposed = true;
|
|
8415
|
+
hostAuthorityEpoch += 1;
|
|
8233
8416
|
abortController.abort();
|
|
8234
|
-
lifecycleManager.dispose(actionContext);
|
|
8235
8417
|
disposeChartsIn(container);
|
|
8236
|
-
container.
|
|
8418
|
+
container.replaceChildren();
|
|
8419
|
+
for (const record of lifecycleRecords.values())
|
|
8420
|
+
destroyLifecycle(record);
|
|
8237
8421
|
},
|
|
8238
8422
|
updateVariables(newVars) {
|
|
8423
|
+
if (disposed)
|
|
8424
|
+
return;
|
|
8425
|
+
hostAuthorityEpoch += 1;
|
|
8239
8426
|
variables = { ...variables, ...newVars };
|
|
8240
8427
|
rerender();
|
|
8241
8428
|
},
|
|
@@ -8313,7 +8500,7 @@ function restoreMediaStates(root, states) {
|
|
|
8313
8500
|
});
|
|
8314
8501
|
}
|
|
8315
8502
|
// ─── Recursive Node Renderer ─────────────────────────────────────
|
|
8316
|
-
function renderNode(node, variables, actionContext, isMobile, responsive,
|
|
8503
|
+
function renderNode(node, variables, actionContext, isMobile, responsive, schemaActions, writeInputVariable, runEventActions, collectLifecycle) {
|
|
8317
8504
|
// Check directives.visible
|
|
8318
8505
|
if (node.directives?.visible) {
|
|
8319
8506
|
const visibleExpr = node.directives.visible;
|
|
@@ -8371,7 +8558,7 @@ function renderNode(node, variables, actionContext, isMobile, responsive, lifecy
|
|
|
8371
8558
|
?? e.target?.value;
|
|
8372
8559
|
if (value !== undefined) {
|
|
8373
8560
|
// Write silently — do NOT trigger rerender (avoids losing focus)
|
|
8374
|
-
|
|
8561
|
+
writeInputVariable(variableKey, value, variables);
|
|
8375
8562
|
}
|
|
8376
8563
|
}));
|
|
8377
8564
|
}
|
|
@@ -8393,21 +8580,17 @@ function renderNode(node, variables, actionContext, isMobile, responsive, lifecy
|
|
|
8393
8580
|
&& VALUE_CONTROL_TYPES.has(node.type));
|
|
8394
8581
|
if (ownsValueEvent && e.target !== el)
|
|
8395
8582
|
return;
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
variables._event = e.detail;
|
|
8399
|
-
}
|
|
8400
|
-
runActionSteps(resolvedSteps, actionContext);
|
|
8583
|
+
const detail = e instanceof CustomEvent ? e.detail : undefined;
|
|
8584
|
+
runEventActions(resolvedSteps, detail);
|
|
8401
8585
|
}));
|
|
8402
8586
|
}
|
|
8403
8587
|
}
|
|
8404
8588
|
// Register lifecycle
|
|
8405
8589
|
if (node.lifecycle) {
|
|
8406
|
-
|
|
8407
|
-
lifecycleManager.mount(node.id, actionContext);
|
|
8590
|
+
collectLifecycle(node.id, node.lifecycle);
|
|
8408
8591
|
}
|
|
8409
8592
|
// Render children — use slot layout if applicable, otherwise flat append
|
|
8410
|
-
const renderChild = (child) => renderNode(child, variables, actionContext, isMobile, responsive,
|
|
8593
|
+
const renderChild = (child) => renderNode(child, variables, actionContext, isMobile, responsive, schemaActions, writeInputVariable, runEventActions, collectLifecycle);
|
|
8411
8594
|
// Build children-by-id map for layouts that reference IDs (columns groups, float overlays)
|
|
8412
8595
|
const childrenMap = {};
|
|
8413
8596
|
for (const child of node.children) {
|
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",
|