@antglobal/copilot-cards-web 0.0.20260828032020-dev.9 → 0.0.20260915071159-dev.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ The package includes the core schema and action APIs, responsive rendering, Shad
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install @antglobal/copilot-cards-web@1.0.8
10
+ npm install @antglobal/copilot-cards-web@1.0.9
11
11
  ```
12
12
 
13
13
  `@antglobal/copilot-cards-core` is installed automatically.
package/dist/index.d.ts CHANGED
@@ -107,6 +107,8 @@ declare function createResponsiveContext(isMobile: boolean, responsive?: Respons
107
107
  */
108
108
 
109
109
  interface RenderCardOptions extends WebActionContextOptions {
110
+ /** Block whole-card interaction and add a configurable overlay without restyling nodes. Default false. */
111
+ disabled?: boolean;
110
112
  /** Enable mobile component layout explicitly (default false). */
111
113
  isMobile?: boolean;
112
114
  /** Optional mobile sizing conversion; omitted values preserve px output. */
@@ -117,6 +119,8 @@ interface RenderCardOptions extends WebActionContextOptions {
117
119
  botId?: string;
118
120
  }
119
121
  interface CardInstance {
122
+ /** Toggle whole-card interaction without changing schema or component business rules. */
123
+ setDisabled: (disabled: boolean) => void;
120
124
  /** Dispose the card: unmount all lifecycles, remove DOM, detach listeners. */
121
125
  dispose: () => void;
122
126
  /** Update variables and re-render. */
@@ -509,6 +513,8 @@ declare abstract class BaseElement extends HTMLElement {
509
513
  * Subclasses must implement this to render their UI into `this.shadowRoot`.
510
514
  */
511
515
  protected abstract render(): void;
516
+ /** @internal Close temporary interaction UI without clearing component values. */
517
+ closeTransientUI(): void;
512
518
  /** Convert any content value to a safe display string. */
513
519
  protected resolveContent(content: any): string;
514
520
  /**
@@ -736,6 +742,7 @@ declare class CardImage extends BaseElement {
736
742
  private bindEvents;
737
743
  private openLightbox;
738
744
  private closeLightbox;
745
+ closeTransientUI(): void;
739
746
  disconnectedCallback(): void;
740
747
  }
741
748
 
@@ -899,6 +906,7 @@ declare class CardSelect extends BaseElement {
899
906
  private _onDocClick;
900
907
  static readonly is = "ai-card-select";
901
908
  protected render(): void;
909
+ closeTransientUI(): void;
902
910
  disconnectedCallback(): void;
903
911
  updateProps(props: Record<string, any>, isMobile?: boolean, responsive?: ResponsiveOptions): void;
904
912
  private _setOpen;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getBuiltinIcon, cloneJsonData, createLifecycleManager, materializeCard, findAffectedRepeatOwners, createA2UIParameterResolver, createExpressionContext, replaceRootContents, getByJsonPointer, resolveA2UIDeep, resolveDeep, resolveExpressionValue, resolveActionRef, isBoundRenderTreeNode, hasExpression, resolveExpression, runActionSteps, normalizeSchema, validateSchema, requiresBindingMaterialization, parseSchema, StreamingParser, StreamingEngine, bindingTopologyFingerprint, findTemplateRepeatOwners, extractPartialSchema, runActionStep, registry } from '@antglobal/copilot-cards-core';
1
+ import { getBuiltinIcon, cloneJsonData, createLifecycleManager, materializeCard, findAffectedRepeatOwners, createA2UIParameterResolver, createExpressionContext, replaceRootContents, getByJsonPointer, resolveA2UIDeep, resolveDeep, resolveExpressionValue, isBoundRenderTreeNode, resolveActionRef, hasExpression, resolveExpression, runActionSteps, normalizeSchema, validateSchema, requiresBindingMaterialization, parseSchema, StreamingParser, StreamingEngine, 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';
@@ -713,6 +713,269 @@ function buildHeatmapOption(config) {
713
713
  };
714
714
  }
715
715
 
716
+ const controllers = new WeakMap();
717
+ const interactionCleanups = new WeakMap();
718
+ /** Cancel slot-owned gestures before covering the card. */
719
+ function registerCardDisableCleanup(element, cleanup) {
720
+ let callbacks = interactionCleanups.get(element);
721
+ if (!callbacks) {
722
+ callbacks = new Set();
723
+ interactionCleanups.set(element, callbacks);
724
+ }
725
+ callbacks.add(cleanup);
726
+ return () => { callbacks.delete(cleanup); };
727
+ }
728
+ function composedParent(node) {
729
+ return node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);
730
+ }
731
+ function isCardDisabled(node) {
732
+ for (let current = node; current; current = composedParent(current)) {
733
+ if (current instanceof HTMLElement && controllers.get(current)?.isDisabled)
734
+ return true;
735
+ }
736
+ return false;
737
+ }
738
+ const BLOCKED_EVENTS = [
739
+ 'click', 'dblclick', 'auxclick', 'contextmenu',
740
+ 'beforeinput', 'input', 'change', 'submit', 'keydown', 'keyup',
741
+ 'compositionstart', 'compositionupdate', 'compositionend',
742
+ 'choice-request-change', 'choice-navigate',
743
+ ];
744
+ const TOUCH_EVENTS = ['pointerdown', 'pointerup', 'touchstart', 'touchend', 'mousedown', 'mouseup', 'wheel'];
745
+ const TAB_TARGETS = [
746
+ 'a[href]', 'area[href]', 'button', 'input', 'select', 'textarea', 'summary',
747
+ 'iframe', 'object', 'embed', 'audio[controls]', 'video[controls]',
748
+ '[tabindex]', '[contenteditable]:not([contenteditable="false"])',
749
+ ].join(',');
750
+ const CARD_DISABLED_ATTRIBUTE = 'data-card-disabled';
751
+ function restoreAttribute(element, name, value) {
752
+ if (value === null)
753
+ element.removeAttribute(name);
754
+ else
755
+ element.setAttribute(name, value);
756
+ }
757
+ function createCardDisabledController(container, initiallyDisabled = false) {
758
+ let disabled = false;
759
+ let disposed = false;
760
+ let epoch = 0;
761
+ let updateDepth = 0;
762
+ let applyingPresentation = false;
763
+ let previousAriaDisabled = null;
764
+ let previousDisabledMarker = null;
765
+ let overlay;
766
+ let mountStyles = [];
767
+ let tabSnapshots = [];
768
+ let tabObserver;
769
+ const block = (event) => {
770
+ if (!disabled || disposed)
771
+ return;
772
+ event.preventDefault();
773
+ event.stopImmediatePropagation();
774
+ };
775
+ // Prevent component gesture handlers without cancelling the browser's scroll.
776
+ const stopGesture = (event) => {
777
+ if (disabled && !disposed)
778
+ event.stopImmediatePropagation();
779
+ };
780
+ const blockFocus = (event) => {
781
+ if (!disabled || disposed)
782
+ return;
783
+ const target = event.composedPath()[0] ?? event.target;
784
+ if (target instanceof HTMLElement)
785
+ target.blur();
786
+ event.stopImmediatePropagation();
787
+ };
788
+ function closeTransientUI(root) {
789
+ for (const element of root.querySelectorAll('*')) {
790
+ interactionCleanups.get(element)?.forEach(cleanup => cleanup());
791
+ const component = element;
792
+ component.closeTransientUI?.();
793
+ if (element.shadowRoot)
794
+ closeTransientUI(element.shadowRoot);
795
+ }
796
+ }
797
+ function blurCardInput() {
798
+ let active = container.ownerDocument.activeElement;
799
+ while (active?.shadowRoot?.activeElement)
800
+ active = active.shadowRoot.activeElement;
801
+ if (!(active instanceof HTMLElement))
802
+ return;
803
+ for (let node = active; node; node = composedParent(node)) {
804
+ if (node === container) {
805
+ active.blur();
806
+ return;
807
+ }
808
+ }
809
+ }
810
+ function excludeFromTabNavigation(root) {
811
+ const observer = tabObserver ?? (tabObserver = new MutationObserver(() => {
812
+ if (!disabled || disposed || updateDepth > 0)
813
+ return;
814
+ // Components such as streaming Markdown also update their own Shadow DOM.
815
+ restoreTabNavigation();
816
+ excludeFromTabNavigation(container);
817
+ }));
818
+ const targets = [];
819
+ const collect = (element) => {
820
+ if (element.matches(TAB_TARGETS)) {
821
+ targets.push(element);
822
+ return;
823
+ }
824
+ // Browsers can put scroll containers in the Tab order without tabindex.
825
+ const style = element.ownerDocument.defaultView?.getComputedStyle(element);
826
+ if (style && [style.overflowX, style.overflowY].some(value => value === 'auto' || value === 'scroll')) {
827
+ targets.push(element);
828
+ }
829
+ };
830
+ const walk = (parent) => {
831
+ observer.observe(parent, { childList: true, subtree: true });
832
+ if (parent instanceof HTMLElement)
833
+ collect(parent);
834
+ for (const element of parent.querySelectorAll('*')) {
835
+ collect(element);
836
+ if (element.shadowRoot)
837
+ walk(element.shadowRoot);
838
+ }
839
+ };
840
+ // Collect before writing attributes so style reads stay in one batch.
841
+ walk(root);
842
+ for (const element of targets) {
843
+ tabSnapshots.push({ element, tabindex: element.getAttribute('tabindex') });
844
+ element.setAttribute('tabindex', '-1');
845
+ }
846
+ }
847
+ function restoreTabNavigation() {
848
+ tabObserver?.disconnect();
849
+ if (!disabled)
850
+ tabObserver = undefined;
851
+ for (const { element, tabindex } of tabSnapshots) {
852
+ restoreAttribute(element, 'tabindex', tabindex);
853
+ }
854
+ tabSnapshots = [];
855
+ }
856
+ function restorePresentation() {
857
+ restoreTabNavigation();
858
+ overlay?.remove();
859
+ for (const [name, value, priority] of mountStyles) {
860
+ if (value)
861
+ container.style.setProperty(name, value, priority);
862
+ else
863
+ container.style.removeProperty(name);
864
+ }
865
+ mountStyles = [];
866
+ }
867
+ function coverCard() {
868
+ const root = container.firstElementChild;
869
+ if (!root)
870
+ return;
871
+ const style = container.ownerDocument.defaultView?.getComputedStyle(container);
872
+ const ownStyle = (name, value) => {
873
+ mountStyles.push([name, container.style.getPropertyValue(name), container.style.getPropertyPriority(name)]);
874
+ container.style.setProperty(name, value, 'important');
875
+ };
876
+ if (!style?.position || style.position === 'static')
877
+ ownStyle('position', 'relative');
878
+ // Keep the mask above card content without covering neighboring messages.
879
+ ownStyle('isolation', 'isolate');
880
+ overlay ?? (overlay = container.ownerDocument.createElement('div'));
881
+ overlay.setAttribute('data-card-disabled-overlay', '');
882
+ overlay.setAttribute('aria-hidden', 'true');
883
+ overlay.style.cssText = 'position:absolute;inset:0;z-index:2147483647;pointer-events:auto;cursor:default;'
884
+ + 'background:var(--card-disabled-overlay-background, rgba(255, 255, 255, 0.6));';
885
+ // A dedicated mount normally shares the root card's bounds. Respect a mount
886
+ // radius when provided, otherwise match the rendered card's rounded corners.
887
+ const rootStyle = container.ownerDocument.defaultView?.getComputedStyle(root);
888
+ overlay.style.borderRadius = style?.borderRadius && style.borderRadius !== '0px'
889
+ ? style.borderRadius : rootStyle?.borderRadius ?? '';
890
+ container.appendChild(overlay);
891
+ }
892
+ function applyPresentation() {
893
+ if (!disabled || disposed || updateDepth > 0 || applyingPresentation)
894
+ return;
895
+ applyingPresentation = true;
896
+ try {
897
+ // Blur/popup callbacks can synchronously update or toggle the card.
898
+ // Reattach only after renderer updates and popup/gesture cleanup finish.
899
+ restorePresentation();
900
+ closeTransientUI(container);
901
+ blurCardInput();
902
+ if (!disabled || disposed)
903
+ return;
904
+ coverCard();
905
+ excludeFromTabNavigation(container);
906
+ }
907
+ finally {
908
+ applyingPresentation = false;
909
+ }
910
+ }
911
+ function listen(enabled) {
912
+ for (const name of BLOCKED_EVENTS) {
913
+ if (enabled)
914
+ container.addEventListener(name, block, true);
915
+ else
916
+ container.removeEventListener(name, block, true);
917
+ }
918
+ for (const name of TOUCH_EVENTS) {
919
+ if (enabled)
920
+ container.addEventListener(name, stopGesture, { capture: true, passive: true });
921
+ else
922
+ container.removeEventListener(name, stopGesture, true);
923
+ }
924
+ if (enabled)
925
+ container.addEventListener('focus', blockFocus, true);
926
+ else
927
+ container.removeEventListener('focus', blockFocus, true);
928
+ }
929
+ const controller = {
930
+ get isDisabled() { return disabled && !disposed; },
931
+ get epoch() { return epoch; },
932
+ setDisabled(value) {
933
+ if (disposed || disabled === value)
934
+ return;
935
+ disabled = value;
936
+ epoch += 1;
937
+ if (disabled) {
938
+ previousAriaDisabled = container.getAttribute('aria-disabled');
939
+ previousDisabledMarker = container.getAttribute(CARD_DISABLED_ATTRIBUTE);
940
+ container.setAttribute('aria-disabled', 'true');
941
+ container.setAttribute(CARD_DISABLED_ATTRIBUTE, 'true');
942
+ listen(true);
943
+ blurCardInput();
944
+ applyPresentation();
945
+ }
946
+ else {
947
+ restorePresentation();
948
+ restoreAttribute(container, 'aria-disabled', previousAriaDisabled);
949
+ restoreAttribute(container, CARD_DISABLED_ATTRIBUTE, previousDisabledMarker);
950
+ listen(false);
951
+ }
952
+ },
953
+ update(render) {
954
+ if (updateDepth === 0)
955
+ restorePresentation();
956
+ updateDepth += 1;
957
+ try {
958
+ return render();
959
+ }
960
+ finally {
961
+ updateDepth -= 1;
962
+ if (updateDepth === 0)
963
+ applyPresentation();
964
+ }
965
+ },
966
+ dispose() {
967
+ if (disposed)
968
+ return;
969
+ controller.setDisabled(false);
970
+ disposed = true;
971
+ controllers.delete(container);
972
+ },
973
+ };
974
+ controllers.set(container, controller);
975
+ controller.setDisabled(initiallyDisabled);
976
+ return controller;
977
+ }
978
+
716
979
  /**
717
980
  * Slot Layout Renderers — each slot key maps to a specific layout strategy.
718
981
  *
@@ -1033,6 +1296,15 @@ function mergePresentationOwnership(...ownership) {
1033
1296
  styleProperties: Array.from(new Set(ownership.flatMap(value => value.styleProperties))),
1034
1297
  };
1035
1298
  }
1299
+ const datePickerRowPaddingPatches = new WeakMap();
1300
+ /** Suspend an open date picker's row padding around renderer-owned style updates. */
1301
+ function registerDatePickerRowPaddingPatch(row, suspend) {
1302
+ datePickerRowPaddingPatches.set(row, suspend);
1303
+ return () => {
1304
+ if (datePickerRowPaddingPatches.get(row) === suspend)
1305
+ datePickerRowPaddingPatches.delete(row);
1306
+ };
1307
+ }
1036
1308
  /** Patch renderer-owned shell presentation without overwriting layout state. */
1037
1309
  function patchElementShellPresentation(current, previous, desired, ...preservedOwnership) {
1038
1310
  const ownership = mergePresentationOwnership(...preservedOwnership);
@@ -1068,43 +1340,49 @@ function patchElementShellPresentation(current, previous, desired, ...preservedO
1068
1340
  else
1069
1341
  current.removeAttribute(name);
1070
1342
  }
1071
- const previousPropertyNames = stylePropertyNames(previous.style);
1072
- const desiredPropertyNames = stylePropertyNames(desired.style);
1073
- const propertyNames = new Set([
1074
- ...previousPropertyNames,
1075
- ...desiredPropertyNames,
1076
- ]);
1077
- const currentPropertyNames = stylePropertyNames(current.style);
1078
- for (const name of propertyNames) {
1079
- if (layoutStyleProperties.has(name))
1080
- continue;
1081
- const previousHas = previousPropertyNames.has(name);
1082
- const desiredHas = desiredPropertyNames.has(name);
1083
- const previousValue = previous.style.getPropertyValue(name);
1084
- const desiredValue = desired.style.getPropertyValue(name);
1085
- const previousPriority = previous.style.getPropertyPriority(name);
1086
- const desiredPriority = desired.style.getPropertyPriority(name);
1087
- if (previousHas === desiredHas
1088
- && previousValue === desiredValue
1089
- && previousPriority === desiredPriority) {
1090
- continue;
1091
- }
1092
- if (previousHas) {
1093
- if (!currentPropertyNames.has(name)
1094
- || current.style.getPropertyValue(name) !== previousValue
1095
- || current.style.getPropertyPriority(name) !== previousPriority) {
1343
+ const resumeDatePickerPadding = datePickerRowPaddingPatches.get(current)?.();
1344
+ try {
1345
+ const previousPropertyNames = stylePropertyNames(previous.style);
1346
+ const desiredPropertyNames = stylePropertyNames(desired.style);
1347
+ const propertyNames = new Set([
1348
+ ...previousPropertyNames,
1349
+ ...desiredPropertyNames,
1350
+ ]);
1351
+ const currentPropertyNames = stylePropertyNames(current.style);
1352
+ for (const name of propertyNames) {
1353
+ if (layoutStyleProperties.has(name))
1354
+ continue;
1355
+ const previousHas = previousPropertyNames.has(name);
1356
+ const desiredHas = desiredPropertyNames.has(name);
1357
+ const previousValue = previous.style.getPropertyValue(name);
1358
+ const desiredValue = desired.style.getPropertyValue(name);
1359
+ const previousPriority = previous.style.getPropertyPriority(name);
1360
+ const desiredPriority = desired.style.getPropertyPriority(name);
1361
+ if (previousHas === desiredHas
1362
+ && previousValue === desiredValue
1363
+ && previousPriority === desiredPriority) {
1096
1364
  continue;
1097
1365
  }
1366
+ if (previousHas) {
1367
+ if (!currentPropertyNames.has(name)
1368
+ || current.style.getPropertyValue(name) !== previousValue
1369
+ || current.style.getPropertyPriority(name) !== previousPriority) {
1370
+ continue;
1371
+ }
1372
+ }
1373
+ else if (currentPropertyNames.has(name)) {
1374
+ continue;
1375
+ }
1376
+ if (desiredHas) {
1377
+ current.style.setProperty(name, desiredValue, desiredPriority);
1378
+ }
1379
+ else {
1380
+ current.style.removeProperty(name);
1381
+ }
1098
1382
  }
1099
- else if (currentPropertyNames.has(name)) {
1100
- continue;
1101
- }
1102
- if (desiredHas) {
1103
- current.style.setProperty(name, desiredValue, desiredPriority);
1104
- }
1105
- else {
1106
- current.style.removeProperty(name);
1107
- }
1383
+ }
1384
+ finally {
1385
+ resumeDatePickerPadding?.();
1108
1386
  }
1109
1387
  }
1110
1388
  /** Release slot-owned item listeners within a disposed subtree. */
@@ -1391,15 +1669,29 @@ function renderHorizontalScrollSlot(container, children, slotContent, renderChil
1391
1669
  e.preventDefault();
1392
1670
  track.scrollLeft = scrollLeft - (e.pageX - startX);
1393
1671
  };
1394
- const stopDrag = () => {
1672
+ const cancelDrag = () => {
1395
1673
  if (!isDragging)
1396
1674
  return;
1397
1675
  isDragging = false;
1398
1676
  track.style.cursor = 'grab';
1399
1677
  track.style.removeProperty('user-select');
1678
+ };
1679
+ const stopDrag = () => {
1680
+ if (!isDragging)
1681
+ return;
1682
+ cancelDrag();
1400
1683
  if (snap)
1401
1684
  track.style.scrollSnapType = 'x mandatory';
1402
1685
  };
1686
+ // Leave snap suspended on disable so the viewport does not jump. The next
1687
+ // normal drag/wheel completion restores it.
1688
+ ownerCleanup.add(registerCardDisableCleanup(track, () => {
1689
+ cancelDrag();
1690
+ if (wheelTimer) {
1691
+ window.clearTimeout(wheelTimer);
1692
+ wheelTimer = 0;
1693
+ }
1694
+ }));
1403
1695
  addSlotOwnerListener(ownerCleanup, track, 'mousedown', handleMouseDown);
1404
1696
  addSlotOwnerListener(ownerCleanup, track, 'mousemove', handleMouseMove);
1405
1697
  addSlotOwnerListener(ownerCleanup, track, 'mouseup', stopDrag);
@@ -1963,6 +2255,7 @@ function renderCarouselSlot(container, children, _slotContent, props, renderChil
1963
2255
  let isDragging = false;
1964
2256
  let startX = 0;
1965
2257
  let scrollStart = 0;
2258
+ let wheelTimer = 0;
1966
2259
  const handleMouseDown = (event) => {
1967
2260
  if (ownerDisposed)
1968
2261
  return;
@@ -1993,12 +2286,46 @@ function renderCarouselSlot(container, children, _slotContent, props, renderChil
1993
2286
  if (!ownerDisposed)
1994
2287
  snapToNearest();
1995
2288
  };
2289
+ ownerCleanup.add(registerCardDisableCleanup(container, () => {
2290
+ if (isDragging) {
2291
+ isDragging = false;
2292
+ container.style.cursor = 'grab';
2293
+ }
2294
+ if (wheelTimer) {
2295
+ window.clearTimeout(wheelTimer);
2296
+ wheelTimer = 0;
2297
+ }
2298
+ const wasSnapping = pendingSnapScrollEnd !== null || pendingSnapTimer !== 0;
2299
+ clearPendingSnap();
2300
+ // Cancel the browser's in-flight animation as well as its completion callbacks.
2301
+ // The next normal interaction restores snap through snapToNearest.
2302
+ if (wasSnapping) {
2303
+ const left = container.scrollLeft;
2304
+ const overflowX = container.style.getPropertyValue('overflow-x');
2305
+ const priority = container.style.getPropertyPriority('overflow-x');
2306
+ // Safari can deliver a stale compositor frame after scrollTo('instant').
2307
+ // Flush the scroll-container change synchronously so no delayed correction
2308
+ // can overwrite a fresh gesture or API navigation after re-enabling.
2309
+ container.style.setProperty('overflow-x', 'hidden', 'important');
2310
+ try {
2311
+ void container.offsetWidth;
2312
+ container.scrollTo({ left, behavior: 'instant' });
2313
+ }
2314
+ finally {
2315
+ if (overflowX)
2316
+ container.style.setProperty('overflow-x', overflowX, priority);
2317
+ else
2318
+ container.style.removeProperty('overflow-x');
2319
+ }
2320
+ void container.offsetWidth;
2321
+ container.scrollTo({ left, behavior: 'instant' });
2322
+ }
2323
+ }));
1996
2324
  addSlotOwnerListener(ownerCleanup, container, 'mousedown', handleMouseDown);
1997
2325
  addSlotOwnerListener(ownerCleanup, document, 'mousemove', handleMouseMove);
1998
2326
  addSlotOwnerListener(ownerCleanup, document, 'mouseup', handleMouseUp);
1999
2327
  addSlotOwnerListener(ownerCleanup, container, 'touchend', handleTouchEnd, { passive: true });
2000
2328
  // Wheel support
2001
- let wheelTimer = 0;
2002
2329
  const handleWheel = (event) => {
2003
2330
  if (ownerDisposed)
2004
2331
  return;
@@ -2396,6 +2723,8 @@ class BaseElement extends HTMLElement {
2396
2723
  this._responsive = createResponsiveContext(this._isMobile, this._responsiveOptions);
2397
2724
  this.render();
2398
2725
  }
2726
+ /** @internal Close temporary interaction UI without clearing component values. */
2727
+ closeTransientUI() { }
2399
2728
  // ─── Helpers ────────────────────────────────────────────────
2400
2729
  /** Convert any content value to a safe display string. */
2401
2730
  resolveContent(content) {
@@ -3856,7 +4185,8 @@ class CardInput extends BaseElement {
3856
4185
  requestAutoFocus() {
3857
4186
  if (!this.isConnected
3858
4187
  || !this._props.autoFocus
3859
- || this.hasAttribute('data-disabled')) {
4188
+ || this.hasAttribute('data-disabled')
4189
+ || isCardDisabled(this)) {
3860
4190
  return false;
3861
4191
  }
3862
4192
  const control = this.shadowRoot?.querySelector('.card-input');
@@ -4182,6 +4512,10 @@ class CardImage extends BaseElement {
4182
4512
  document.body.style.overflow = '';
4183
4513
  document.removeEventListener('keydown', this._handleEscape);
4184
4514
  }
4515
+ closeTransientUI() {
4516
+ if (this._lightbox?.classList.contains('open'))
4517
+ this.closeLightbox(this._lightbox);
4518
+ }
4185
4519
  disconnectedCallback() {
4186
4520
  // Ensure body overflow is restored when element is removed
4187
4521
  document.body.style.overflow = '';
@@ -5118,6 +5452,9 @@ class CardSelect extends BaseElement {
5118
5452
  });
5119
5453
  });
5120
5454
  }
5455
+ closeTransientUI() {
5456
+ this._setOpen(false);
5457
+ }
5121
5458
  disconnectedCallback() {
5122
5459
  document.removeEventListener('click', this._onDocClick);
5123
5460
  }
@@ -7222,17 +7559,85 @@ const CHEVRON_ICON = `
7222
7559
  <svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
7223
7560
  <path d="M2.5 4.5L6 8l3.5-3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
7224
7561
  </svg>`;
7562
+ // Opening another picker starts before the document click closes the first one.
7563
+ // Share the row reservation so that this overlap cannot accumulate padding.
7564
+ const mobilePanelRows = new WeakMap();
7565
+ function updateRowSpace(row, space) {
7566
+ // Preserve host style changes made while the calendar owns extra padding.
7567
+ const padding = row.style.getPropertyValue('padding-bottom');
7568
+ const priority = row.style.getPropertyPriority('padding-bottom');
7569
+ if (padding !== space.appliedPaddingBottom || priority !== space.priority) {
7570
+ space.paddingBottom = padding;
7571
+ space.priority = priority;
7572
+ space.basePaddingBottom = padding || getComputedStyle(row).paddingBottom;
7573
+ }
7574
+ if (space.panels.size) {
7575
+ const height = Math.max(...space.panels.values());
7576
+ row.style.setProperty('padding-bottom', `calc(${space.basePaddingBottom} + ${height}px)`, space.priority);
7577
+ }
7578
+ else {
7579
+ if (space.paddingBottom)
7580
+ row.style.setProperty('padding-bottom', space.paddingBottom, space.priority);
7581
+ else
7582
+ row.style.removeProperty('padding-bottom');
7583
+ mobilePanelRows.delete(row);
7584
+ space.releasePatch();
7585
+ }
7586
+ // CSSOM can simplify calc(); compare its serialized value on the next write.
7587
+ space.appliedPaddingBottom = row.style.getPropertyValue('padding-bottom');
7588
+ }
7225
7589
  class CardDatePicker extends BaseElement {
7226
7590
  constructor() {
7227
7591
  super(...arguments);
7228
7592
  this._open = false;
7229
7593
  this._calendar = null;
7594
+ this._panelResizeObserver = null;
7595
+ this._mobilePanelRow = null;
7230
7596
  /** Dates picked in the panel; shown until props.value changes externally. */
7231
7597
  this._localValue = null;
7232
7598
  this._onDocClick = (e) => {
7233
- if (!this.contains(e.target))
7599
+ if (!e.composedPath().includes(this))
7234
7600
  this._setOpen(false);
7235
7601
  };
7602
+ this._positionMobilePanel = () => {
7603
+ if (!this._isMobile || !this._open)
7604
+ return;
7605
+ const panel = this.shadowRoot?.querySelector('.panel');
7606
+ const calendar = panel?.querySelector('[data-vc="calendar"]');
7607
+ if (!panel || !calendar)
7608
+ return;
7609
+ this._panelResizeObserver?.observe(calendar);
7610
+ const row = this._mobilePanelRow;
7611
+ if (row) {
7612
+ const style = getComputedStyle(row);
7613
+ const contentBottom = row.getBoundingClientRect().bottom
7614
+ - parseFloat(style.paddingBottom) - parseFloat(style.borderBottomWidth);
7615
+ panel.style.top = `${contentBottom - this.getBoundingClientRect().top}px`;
7616
+ const space = mobilePanelRows.get(row);
7617
+ space.panels.set(this, calendar.getBoundingClientRect().bottom - contentBottom);
7618
+ updateRowSpace(row, space);
7619
+ }
7620
+ panel.style.left = '0px';
7621
+ const bounds = calendar.getBoundingClientRect();
7622
+ let left = 0;
7623
+ let right = document.documentElement.clientWidth;
7624
+ // Intersect clipping ancestors, crossing shadow roots when cards are embedded.
7625
+ let ancestor = this.shadowRoot.host;
7626
+ while (ancestor) {
7627
+ this._panelResizeObserver?.observe(ancestor);
7628
+ if (/^(auto|scroll|hidden|clip)$/.test(getComputedStyle(ancestor).overflowX)) {
7629
+ const rect = ancestor.getBoundingClientRect();
7630
+ const innerLeft = rect.left + ancestor.clientLeft;
7631
+ left = Math.max(left, innerLeft);
7632
+ right = Math.min(right, innerLeft + ancestor.clientWidth);
7633
+ }
7634
+ const root = ancestor.getRootNode();
7635
+ ancestor = ancestor.assignedSlot ?? ancestor.parentElement
7636
+ ?? (root instanceof ShadowRoot ? root.host : null);
7637
+ }
7638
+ // Shift the panel without changing the trigger's intrinsic width.
7639
+ panel.style.left = `${Math.max(left, Math.min(bounds.left, right - bounds.width)) - bounds.left}px`;
7640
+ };
7236
7641
  }
7237
7642
  render() {
7238
7643
  if (!this.shadowRoot || !this._node)
@@ -7333,8 +7738,11 @@ class CardDatePicker extends BaseElement {
7333
7738
  .trigger-arrow.open { transform: rotate(180deg); }
7334
7739
  .panel {
7335
7740
  display: none;
7336
- position: absolute;
7337
- top: calc(100% + 4px);
7741
+ /* Column layouts include the calendar in flow. Horizontal flex rows
7742
+ reserve space below the whole row when the panel is mounted. */
7743
+ position: ${this._isMobile ? 'relative' : 'absolute'};
7744
+ ${this._isMobile ? 'margin-top: 4px; contain: inline-size;' : ''}
7745
+ top: ${this._isMobile ? '0' : 'calc(100% + 4px)'};
7338
7746
  left: 0;
7339
7747
  z-index: 1000;
7340
7748
  }
@@ -7386,6 +7794,9 @@ class CardDatePicker extends BaseElement {
7386
7794
  this._destroyCalendar();
7387
7795
  }
7388
7796
  // ─── Private ────────────────────────────────────────────────
7797
+ closeTransientUI() {
7798
+ this._setOpen(false);
7799
+ }
7389
7800
  _setOpen(open) {
7390
7801
  if (this._open === open)
7391
7802
  return;
@@ -7467,8 +7878,59 @@ class CardDatePicker extends BaseElement {
7467
7878
  },
7468
7879
  });
7469
7880
  this._calendar.init();
7881
+ if (this._isMobile) {
7882
+ const row = this.parentElement;
7883
+ if (row && /^(inline-)?flex$/.test(getComputedStyle(row).display)
7884
+ && /^row/.test(getComputedStyle(row).flexDirection)) {
7885
+ const panel = container.parentElement;
7886
+ panel.style.position = 'absolute';
7887
+ this._mobilePanelRow = row;
7888
+ if (!mobilePanelRows.has(row)) {
7889
+ const space = {
7890
+ paddingBottom: row.style.getPropertyValue('padding-bottom'),
7891
+ priority: row.style.getPropertyPriority('padding-bottom'),
7892
+ basePaddingBottom: row.style.paddingBottom || getComputedStyle(row).paddingBottom,
7893
+ appliedPaddingBottom: row.style.getPropertyValue('padding-bottom'),
7894
+ panels: new Map(),
7895
+ releasePatch: () => { },
7896
+ };
7897
+ mobilePanelRows.set(row, space);
7898
+ space.releasePatch = registerDatePickerRowPaddingPatch(row, () => {
7899
+ // Make the schema's baseline visible to the incremental style diff.
7900
+ // If a host already replaced our value, leave that external style.
7901
+ if (row.style.getPropertyValue('padding-bottom') === space.appliedPaddingBottom
7902
+ && row.style.getPropertyPriority('padding-bottom') === space.priority) {
7903
+ if (space.paddingBottom)
7904
+ row.style.setProperty('padding-bottom', space.paddingBottom, space.priority);
7905
+ else
7906
+ row.style.removeProperty('padding-bottom');
7907
+ space.appliedPaddingBottom = row.style.getPropertyValue('padding-bottom');
7908
+ }
7909
+ return () => updateRowSpace(row, space);
7910
+ });
7911
+ }
7912
+ }
7913
+ if (typeof ResizeObserver !== 'undefined') {
7914
+ this._panelResizeObserver = new ResizeObserver(this._positionMobilePanel);
7915
+ }
7916
+ // Calendar navigation changes height synchronously, including browsers
7917
+ // without ResizeObserver. Run after the calendar's own click handler.
7918
+ container.parentElement.addEventListener('click', this._positionMobilePanel);
7919
+ window.addEventListener('resize', this._positionMobilePanel);
7920
+ this._positionMobilePanel();
7921
+ }
7470
7922
  }
7471
7923
  _destroyCalendar() {
7924
+ window.removeEventListener('resize', this._positionMobilePanel);
7925
+ this.shadowRoot?.querySelector('.panel')?.removeEventListener('click', this._positionMobilePanel);
7926
+ this._panelResizeObserver?.disconnect();
7927
+ this._panelResizeObserver = null;
7928
+ if (this._mobilePanelRow) {
7929
+ const space = mobilePanelRows.get(this._mobilePanelRow);
7930
+ space.panels.delete(this);
7931
+ updateRowSpace(this._mobilePanelRow, space);
7932
+ this._mobilePanelRow = null;
7933
+ }
7472
7934
  if (this._calendar) {
7473
7935
  this._calendar.destroy();
7474
7936
  this._calendar = null;
@@ -8972,7 +9434,7 @@ function createCardNodeAccess(container) {
8972
9434
  },
8973
9435
  focus(options) {
8974
9436
  const element = resolveElement(id);
8975
- if (!element || element.hasAttribute('data-disabled'))
9437
+ if (!element || element.hasAttribute('data-disabled') || isCardDisabled(element))
8976
9438
  return false;
8977
9439
  const target = element.shadowRoot
8978
9440
  ?.querySelector(FOCUS_TARGET_SELECTOR) ?? element;
@@ -9193,28 +9655,15 @@ function createCardNodeAccess(container) {
9193
9655
  }
9194
9656
 
9195
9657
  const DISABLED_MARKER = 'data-copilot-card-disabled';
9196
- const DISABLED_STYLES_MARKER = 'data-copilot-card-disabled-styles';
9197
- const DISABLED_PRESENTATION_CSS = `
9198
- [${DISABLED_MARKER}="true"] {
9199
- background-color: var(--card-disabled-background, #F5F5F5) !important;
9200
- color: var(--card-disabled-foreground, #C0C0C0) !important;
9201
- }
9202
- `;
9203
- function ensureDisabledPresentationStyles(ownerDocument) {
9204
- if (ownerDocument.head.querySelector(`style[${DISABLED_STYLES_MARKER}]`)) {
9205
- return;
9206
- }
9207
- const style = ownerDocument.createElement('style');
9208
- style.setAttribute(DISABLED_STYLES_MARKER, '');
9209
- style.textContent = DISABLED_PRESENTATION_CSS;
9210
- ownerDocument.head.appendChild(style);
9211
- }
9212
9658
  function applyDisabledState(element, disabled) {
9213
9659
  if (!disabled)
9214
9660
  return;
9215
- ensureDisabledPresentationStyles(element.ownerDocument);
9216
9661
  element.setAttribute('data-disabled', 'true');
9217
9662
  element.setAttribute(DISABLED_MARKER, 'true');
9663
+ // Inline declarations work inside host ShadowRoots too. Use the shorthand
9664
+ // to mask background images/gradients, matching the historical disabled state.
9665
+ element.style.background = 'var(--card-disabled-background, #F5F5F5)';
9666
+ element.style.color = 'var(--card-disabled-foreground, #C0C0C0)';
9218
9667
  element.style.pointerEvents = 'none';
9219
9668
  element.style.cursor = 'default';
9220
9669
  }
@@ -9479,6 +9928,7 @@ function renderBoundCard(container, schema, options) {
9479
9928
  },
9480
9929
  events,
9481
9930
  isActive: () => (!disposed
9931
+ && !cardDisabled.isDisabled
9482
9932
  && !publishing
9483
9933
  && container.contains(element)),
9484
9934
  });
@@ -9982,7 +10432,12 @@ function renderBoundCard(container, schema, options) {
9982
10432
  try {
9983
10433
  for (const [occurrenceKey, plan] of appendPlans) {
9984
10434
  const prepared = findPreparedByOccurrenceKey(preparedRoot, occurrenceKey);
9985
- if (!prepared?.[REPEAT_APPEND]) {
10435
+ // Hidden or force-replaced ancestors prune their descendants from the
10436
+ // prepared tree. Their normal replacement commits the new variables;
10437
+ // there is no live descendant append to prepare in this transaction.
10438
+ if (!prepared)
10439
+ continue;
10440
+ if (!prepared[REPEAT_APPEND]) {
9986
10441
  throw new Error(`[renderCard] Missing prepared repeat append owner "${occurrenceKey}"`);
9987
10442
  }
9988
10443
  const nextOccurrenceKeys = occurrenceKeysForChildren(plan.current.occurrenceKey, plan.nextNode.children);
@@ -10463,7 +10918,7 @@ function renderBoundCard(container, schema, options) {
10463
10918
  error.failures = failures;
10464
10919
  return error;
10465
10920
  }
10466
- function commitIncrementalCandidate(candidate, renderVariables) {
10921
+ function commitIncrementalCandidateContent(candidate, renderVariables) {
10467
10922
  const replacements = candidate.replacementRoots;
10468
10923
  try {
10469
10924
  if (!currentMountedTree) {
@@ -10655,6 +11110,9 @@ function renderBoundCard(container, schema, options) {
10655
11110
  disposeCandidate(candidate);
10656
11111
  }
10657
11112
  }
11113
+ function commitIncrementalCandidate(candidate, renderVariables) {
11114
+ return cardDisabled.update(() => commitIncrementalCandidateContent(candidate, renderVariables));
11115
+ }
10658
11116
  function assertCurrentRevision(baseRevision) {
10659
11117
  if (disposed || publishing || revision !== baseRevision) {
10660
11118
  const error = new Error('[renderCard] BOUND_TRANSACTION_CONFLICT');
@@ -10662,7 +11120,7 @@ function renderBoundCard(container, schema, options) {
10662
11120
  throw error;
10663
11121
  }
10664
11122
  }
10665
- function commitDraft(before, draft, baseRevision) {
11123
+ function commitDraftContent(before, draft, baseRevision) {
10666
11124
  assertCurrentRevision(baseRevision);
10667
11125
  const validationRoot = cloneJsonData(variables);
10668
11126
  replaceRootContents(validationRoot, draft);
@@ -10716,6 +11174,9 @@ function renderBoundCard(container, schema, options) {
10716
11174
  reconcileLifecycles(selectLifecycleNodes(currentMaterialized, collectMountedLifecycleIds(currentMountedTree)));
10717
11175
  applyAutoFocus(candidate);
10718
11176
  }
11177
+ function commitDraft(before, draft, baseRevision) {
11178
+ return cardDisabled.update(() => commitDraftContent(before, draft, baseRevision));
11179
+ }
10719
11180
  function writeDraftVariable(draft, key, value) {
10720
11181
  Object.defineProperty(draft, String(key), {
10721
11182
  value,
@@ -10772,7 +11233,7 @@ function renderBoundCard(container, schema, options) {
10772
11233
  };
10773
11234
  }
10774
11235
  async function runBoundEvent(runtimeId, eventName, eventDetail, baseRevision, repeatTargetFingerprint) {
10775
- if (disposed)
11236
+ if (disposed || cardDisabled.isDisabled)
10776
11237
  return;
10777
11238
  assertCurrentRevision(baseRevision);
10778
11239
  const transactionBefore = cloneJsonData(variables);
@@ -10808,13 +11269,14 @@ function renderBoundCard(container, schema, options) {
10808
11269
  commitDraft(cloneJsonData(lastPublishedVariables), draft, baseRevision);
10809
11270
  }
10810
11271
  function enqueueBoundEvent(runtimeId, eventName, eventDetail, repeatTargetFingerprint) {
10811
- if (disposed)
11272
+ if (disposed || cardDisabled.isDisabled)
10812
11273
  return;
11274
+ const interactionEpoch = cardDisabled.epoch;
10813
11275
  const enqueueRevision = revision;
10814
11276
  const queuedBehindAnotherAction = queuedActionCount > 0;
10815
11277
  queuedActionCount += 1;
10816
11278
  actionQueue = actionQueue
10817
- .then(() => runBoundEvent(runtimeId, eventName, eventDetail, queuedBehindAnotherAction ? revision : enqueueRevision, repeatTargetFingerprint))
11279
+ .then(() => interactionEpoch === cardDisabled.epoch ? runBoundEvent(runtimeId, eventName, eventDetail, queuedBehindAnotherAction ? revision : enqueueRevision, repeatTargetFingerprint) : undefined)
10818
11280
  .catch((error) => {
10819
11281
  if (error
10820
11282
  && typeof error === 'object'
@@ -10846,6 +11308,7 @@ function renderBoundCard(container, schema, options) {
10846
11308
  disposed = true;
10847
11309
  disposeRequested = false;
10848
11310
  nodeAccess.dispose();
11311
+ cardDisabled.dispose();
10849
11312
  abortController.abort();
10850
11313
  if (currentMountedTree) {
10851
11314
  disposeBoundSubtree(currentMountedTree);
@@ -10883,17 +11346,26 @@ function renderBoundCard(container, schema, options) {
10883
11346
  console.error('[renderCard] Bound lifecycle dispose failed', error);
10884
11347
  });
10885
11348
  }
10886
- const initialCandidate = prepareIncrementalCandidate(variables, variables);
10887
- publishing = true;
11349
+ const cardDisabled = createCardDisabledController(container, options.disabled);
10888
11350
  try {
10889
- currentMountedTree = commitIncrementalCandidate(initialCandidate, variables);
10890
- currentMaterialized = materializeCard(schema, variables);
11351
+ const initialCandidate = prepareIncrementalCandidate(variables, variables);
11352
+ publishing = true;
11353
+ try {
11354
+ currentMountedTree = commitIncrementalCandidate(initialCandidate, variables);
11355
+ currentMaterialized = materializeCard(schema, variables);
11356
+ }
11357
+ finally {
11358
+ publishing = false;
11359
+ }
11360
+ reconcileLifecycles(selectLifecycleNodes(currentMaterialized, collectMountedLifecycleIds(currentMountedTree)));
10891
11361
  }
10892
- finally {
10893
- publishing = false;
11362
+ catch (error) {
11363
+ nodeAccess.dispose();
11364
+ cardDisabled.dispose();
11365
+ throw error;
10894
11366
  }
10895
- reconcileLifecycles(selectLifecycleNodes(currentMaterialized, collectMountedLifecycleIds(currentMountedTree)));
10896
11367
  return {
11368
+ setDisabled: cardDisabled.setDisabled,
10897
11369
  getNode: nodeAccess.getNode,
10898
11370
  onFocusChange: nodeAccess.onFocusChange,
10899
11371
  onScroll: nodeAccess.onScroll,
@@ -11261,7 +11733,7 @@ function renderStaticCard(container, schema, options) {
11261
11733
  };
11262
11734
  }
11263
11735
  function runStaticEventActions(steps, detail) {
11264
- if (disposed)
11736
+ if (disposed || cardDisabled.isDisabled)
11265
11737
  return;
11266
11738
  if (detail != null)
11267
11739
  variables._event = detail;
@@ -11400,6 +11872,7 @@ function renderStaticCard(container, schema, options) {
11400
11872
  },
11401
11873
  events: resolvedEventBindings,
11402
11874
  isActive: () => (!disposed
11875
+ && !cardDisabled.isDisabled
11403
11876
  && !publishing
11404
11877
  && container.contains(element)),
11405
11878
  });
@@ -11598,7 +12071,7 @@ function renderStaticCard(container, schema, options) {
11598
12071
  }
11599
12072
  }
11600
12073
  }
11601
- function render() {
12074
+ function renderContent() {
11602
12075
  if (disposed)
11603
12076
  return;
11604
12077
  const generation = ++renderGeneration;
@@ -11692,6 +12165,9 @@ function renderStaticCard(container, schema, options) {
11692
12165
  startLifecycleMount(id, lifecycle);
11693
12166
  }
11694
12167
  }
12168
+ function render() {
12169
+ return cardDisabled.update(() => renderContent());
12170
+ }
11695
12171
  function rerender() {
11696
12172
  if (disposed)
11697
12173
  return;
@@ -11705,6 +12181,7 @@ function renderStaticCard(container, schema, options) {
11705
12181
  disposeRequested = false;
11706
12182
  hostAuthorityEpoch += 1;
11707
12183
  nodeAccess.dispose();
12184
+ cardDisabled.dispose();
11708
12185
  abortController.abort();
11709
12186
  if (mountedTree) {
11710
12187
  disposeStaticSubtree(mountedTree);
@@ -11718,9 +12195,18 @@ function renderStaticCard(container, schema, options) {
11718
12195
  destroyLifecycle(record);
11719
12196
  }
11720
12197
  // 7. Initial render
11721
- render();
12198
+ const cardDisabled = createCardDisabledController(container, options.disabled);
12199
+ try {
12200
+ render();
12201
+ }
12202
+ catch (error) {
12203
+ nodeAccess.dispose();
12204
+ cardDisabled.dispose();
12205
+ throw error;
12206
+ }
11722
12207
  // 8. Return instance handle
11723
12208
  return {
12209
+ setDisabled: cardDisabled.setDisabled,
11724
12210
  getNode: nodeAccess.getNode,
11725
12211
  onFocusChange: nodeAccess.onFocusChange,
11726
12212
  onScroll: nodeAccess.onScroll,
@@ -12101,6 +12587,8 @@ function renderStreamingCard(container, options = {}) {
12101
12587
  const variableKey = resolvedProps.variableKey;
12102
12588
  if (variableKey) {
12103
12589
  element.addEventListener('input', ((event) => {
12590
+ if (disposed || cardDisabled.isDisabled || !container.contains(element))
12591
+ return;
12104
12592
  if (event.target !== element || !currentMaterialized)
12105
12593
  return;
12106
12594
  const value = event.detail?.value
@@ -12116,6 +12604,8 @@ function renderStreamingCard(container, options = {}) {
12116
12604
  }
12117
12605
  const domEvent = eventMap[eventName] ?? eventName;
12118
12606
  element.addEventListener(domEvent, ((event) => {
12607
+ if (disposed || cardDisabled.isDisabled || !container.contains(element))
12608
+ return;
12119
12609
  const ownsValueEvent = ((domEvent === 'input' || domEvent === 'change')
12120
12610
  && valueControlTypes.has(node.type));
12121
12611
  if (ownsValueEvent && event.target !== element)
@@ -12184,6 +12674,8 @@ function renderStreamingCard(container, options = {}) {
12184
12674
  const variableKey = resolvedProps.variableKey;
12185
12675
  if (variableKey) {
12186
12676
  el.addEventListener('input', ((e) => {
12677
+ if (disposed || cardDisabled.isDisabled || !container.contains(el))
12678
+ return;
12187
12679
  const value = e.detail?.value
12188
12680
  ?? e.target?.value;
12189
12681
  if (value !== undefined) {
@@ -12202,6 +12694,8 @@ function renderStreamingCard(container, options = {}) {
12202
12694
  continue;
12203
12695
  const domEvent = eventMap[event] ?? event;
12204
12696
  el.addEventListener(domEvent, ((e) => {
12697
+ if (disposed || cardDisabled.isDisabled || !container.contains(el))
12698
+ return;
12205
12699
  if (e instanceof CustomEvent && e.detail != null) {
12206
12700
  variables._event = e.detail;
12207
12701
  }
@@ -12385,7 +12879,7 @@ function renderStreamingCard(container, options = {}) {
12385
12879
  * (visibility flips / container shape changes) re-render only their subtree.
12386
12880
  * Falls back to a full render solely when a subtree can't be rebuilt.
12387
12881
  */
12388
- function diffAllElements(previousVariables) {
12882
+ function diffAllElementsContent(previousVariables) {
12389
12883
  actionContext = buildActionContext();
12390
12884
  const schema = (currentSurfaceId ? engine.getSchema(currentSurfaceId) : undefined) ?? currentSchema;
12391
12885
  if (!schema)
@@ -12425,6 +12919,8 @@ function renderStreamingCard(container, options = {}) {
12425
12919
  continue;
12426
12920
  if ('updateProps' in el) {
12427
12921
  el.updateProps(resolved, isMobile, options.responsive);
12922
+ // Some components replace host cssText while updating their props.
12923
+ applyDisabledState(el, wasDisabled);
12428
12924
  propsCache.set(id, key);
12429
12925
  }
12430
12926
  else if (!replaceSubtree(schema, id, el)) {
@@ -12435,6 +12931,9 @@ function renderStreamingCard(container, options = {}) {
12435
12931
  }
12436
12932
  applyAutoFocusIds(autoFocusIds);
12437
12933
  }
12934
+ function diffAllElements(previousVariables) {
12935
+ return cardDisabled.update(() => diffAllElementsContent(previousVariables));
12936
+ }
12438
12937
  function captureBoundScrollPositions(root) {
12439
12938
  const positions = new Map();
12440
12939
  root.querySelectorAll('[data-scroll-id]').forEach((element) => {
@@ -12785,7 +13284,7 @@ function renderStreamingCard(container, options = {}) {
12785
13284
  throw error;
12786
13285
  }
12787
13286
  }
12788
- function commitBoundFull(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
13287
+ function commitBoundFullContent(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
12789
13288
  assertBoundRevision(baseRevision);
12790
13289
  commitAuthoritativeVariables?.();
12791
13290
  replaceRootContents(variables, draft);
@@ -12807,6 +13306,9 @@ function renderStreamingCard(container, options = {}) {
12807
13306
  restoreBoundScrollPositions(container, scrollPositions);
12808
13307
  restoreBoundMediaStates(container, mediaStates);
12809
13308
  }
13309
+ function commitBoundFull(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
13310
+ return cardDisabled.update(() => commitBoundFullContent(prepared, schema, draft, baseRevision, commitAuthoritativeVariables));
13311
+ }
12810
13312
  function ownerKeysWithoutNestedDuplicates(materialized, owners) {
12811
13313
  const selected = new Set(owners.map(owner => owner.key));
12812
13314
  return owners.filter((owner) => {
@@ -12926,7 +13428,7 @@ function renderStreamingCard(container, options = {}) {
12926
13428
  replacedLifecycleIds,
12927
13429
  };
12928
13430
  }
12929
- function commitBoundIncremental(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
13431
+ function commitBoundIncrementalContent(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
12930
13432
  assertBoundRevision(baseRevision);
12931
13433
  commitAuthoritativeVariables?.();
12932
13434
  replaceRootContents(variables, draft);
@@ -12957,6 +13459,9 @@ function renderStreamingCard(container, options = {}) {
12957
13459
  restoreBoundScrollPositions(container, scrollPositions);
12958
13460
  restoreBoundMediaStates(container, mediaStates);
12959
13461
  }
13462
+ function commitBoundIncremental(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
13463
+ return cardDisabled.update(() => commitBoundIncrementalContent(prepared, schema, draft, baseRevision, commitAuthoritativeVariables));
13464
+ }
12960
13465
  function prepareAndCommitBoundUpdate(schema, draft, owners, baseRevision, commitAuthoritativeVariables, changedSourceIds = new Set()) {
12961
13466
  try {
12962
13467
  const prepared = prepareBoundIncremental(schema, draft, owners, changedSourceIds);
@@ -13136,6 +13641,8 @@ function renderStreamingCard(container, options = {}) {
13136
13641
  prepareAndCommitBoundUpdate(schema, draft, owners, baseRevision, commitAuthoritative);
13137
13642
  }
13138
13643
  async function runBoundEvent(runtimeId, eventName, eventDetail) {
13644
+ if (disposed || cardDisabled.isDisabled)
13645
+ return;
13139
13646
  if (!currentMaterialized || !currentSchema || !currentSurfaceId)
13140
13647
  return;
13141
13648
  const baseRevision = boundRevision;
@@ -13162,8 +13669,12 @@ function renderStreamingCard(container, options = {}) {
13162
13669
  applyAutoFocusIds(autoFocusIds);
13163
13670
  }
13164
13671
  function enqueueBoundEvent(runtimeId, eventName, eventDetail) {
13672
+ if (disposed || cardDisabled.isDisabled)
13673
+ return;
13674
+ const interactionEpoch = cardDisabled.epoch;
13165
13675
  boundActionQueue = boundActionQueue
13166
- .then(() => runBoundEvent(runtimeId, eventName, eventDetail))
13676
+ .then(() => interactionEpoch === cardDisabled.epoch
13677
+ ? runBoundEvent(runtimeId, eventName, eventDetail) : undefined)
13167
13678
  .catch((error) => {
13168
13679
  if (error
13169
13680
  && typeof error === 'object'
@@ -13338,7 +13849,7 @@ function renderStreamingCard(container, options = {}) {
13338
13849
  * assigned on createSurface — so newly added components would not render and text
13339
13850
  * appended via appendContent would be wiped on the next full re-render.
13340
13851
  */
13341
- function renderFull() {
13852
+ function renderFullContent() {
13342
13853
  const engineSchema = currentSurfaceId ? engine.getSchema(currentSurfaceId) : undefined;
13343
13854
  const schema = engineSchema ?? currentSchema;
13344
13855
  if (!schema)
@@ -13365,6 +13876,9 @@ function renderStreamingCard(container, options = {}) {
13365
13876
  const dom = renderNode(tree);
13366
13877
  container.appendChild(dom);
13367
13878
  }
13879
+ function renderFull() {
13880
+ return cardDisabled.update(() => renderFullContent());
13881
+ }
13368
13882
  /**
13369
13883
  * Full render that tolerates mid-stream schemas (e.g. children referencing
13370
13884
  * elements that have not arrived yet): keeps the previous DOM on failure so
@@ -13381,7 +13895,7 @@ function renderStreamingCard(container, options = {}) {
13381
13895
  }
13382
13896
  }
13383
13897
  // ─── Engine Event Handlers ──────────────────────────────────────
13384
- const engine = new StreamingEngine({
13898
+ const engineEvents = {
13385
13899
  onSurfaceCreated(surfaceId, schemaInput) {
13386
13900
  if (schemaInput) {
13387
13901
  const nextSchema = engine.getSchema(surfaceId)
@@ -13596,6 +14110,7 @@ function renderStreamingCard(container, options = {}) {
13596
14110
  const typeChanged = el.getAttribute('data-card-type') !== element.type;
13597
14111
  if (!typeChanged && 'updateProps' in el) {
13598
14112
  el.updateProps(resolved, isMobile, options.responsive);
14113
+ applyDisabledState(el, wasDisabled);
13599
14114
  propsCache.set(id, key);
13600
14115
  }
13601
14116
  else if (!typeChanged && isChildrenOnlyChange(propsCache.get(id), key, element)) {
@@ -13746,9 +14261,18 @@ function renderStreamingCard(container, options = {}) {
13746
14261
  boundRevision += 1;
13747
14262
  lifecycleManager.dispose(actionContext);
13748
14263
  },
14264
+ };
14265
+ const engine = new StreamingEngine({
14266
+ onSurfaceCreated: (...args) => cardDisabled.update(() => engineEvents.onSurfaceCreated?.(...args)),
14267
+ onComponentsUpdated: (...args) => cardDisabled.update(() => engineEvents.onComponentsUpdated?.(...args)),
14268
+ onDataModelUpdated: (...args) => cardDisabled.update(() => engineEvents.onDataModelUpdated?.(...args)),
14269
+ onContentAppended: (...args) => cardDisabled.update(() => engineEvents.onContentAppended?.(...args)),
14270
+ onSurfaceDeleted: (...args) => cardDisabled.update(() => engineEvents.onSurfaceDeleted?.(...args)),
13749
14271
  });
13750
14272
  // ─── Public API ─────────────────────────────────────────────────
14273
+ const cardDisabled = createCardDisabledController(container, options.disabled);
13751
14274
  return {
14275
+ setDisabled: cardDisabled.setDisabled,
13752
14276
  getNode: nodeAccess.getNode,
13753
14277
  onFocusChange: nodeAccess.onFocusChange,
13754
14278
  onScroll: nodeAccess.onScroll,
@@ -13829,6 +14353,7 @@ function renderStreamingCard(container, options = {}) {
13829
14353
  return;
13830
14354
  disposed = true;
13831
14355
  nodeAccess.dispose();
14356
+ cardDisabled.dispose();
13832
14357
  boundRevision += 1;
13833
14358
  abortController.abort();
13834
14359
  teardownBoundLifecycles();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antglobal/copilot-cards-web",
3
- "version": "0.0.20260828032020-dev.9",
3
+ "version": "0.0.20260915071159-dev.5",
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": "0.0.20260828032020-dev.9",
31
+ "@antglobal/copilot-cards-core": "0.0.20260915071159-dev.5",
32
32
  "echarts": "^5.6.0",
33
33
  "marked": "^18.0.5",
34
34
  "tslib": "^2.8.1",