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

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
+ /** Disable the whole card using the existing project disabled appearance. 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,276 @@ function buildHeatmapOption(config) {
713
713
  };
714
714
  }
715
715
 
716
+ const DISABLED_MARKER = 'data-copilot-card-disabled';
717
+ function applyDisabledState(element, disabled) {
718
+ if (!disabled)
719
+ return;
720
+ element.setAttribute('data-disabled', 'true');
721
+ element.setAttribute(DISABLED_MARKER, 'true');
722
+ // Inline declarations work inside host ShadowRoots too. Use the shorthand
723
+ // to mask background images/gradients, matching the historical disabled state.
724
+ element.style.background = 'var(--card-disabled-background, #F5F5F5)';
725
+ element.style.color = 'var(--card-disabled-foreground, #C0C0C0)';
726
+ element.style.pointerEvents = 'none';
727
+ element.style.cursor = 'default';
728
+ }
729
+ function applyDisabledDescendants(element, disabled) {
730
+ if (!disabled)
731
+ return;
732
+ element.querySelectorAll('*').forEach((child) => {
733
+ child.setAttribute('data-disabled', 'true');
734
+ });
735
+ }
736
+
737
+ const controllers = new WeakMap();
738
+ const interactionCleanups = new WeakMap();
739
+ /** Cancel slot-owned gestures before capturing the disabled presentation. */
740
+ function registerCardDisableCleanup(element, cleanup) {
741
+ let callbacks = interactionCleanups.get(element);
742
+ if (!callbacks) {
743
+ callbacks = new Set();
744
+ interactionCleanups.set(element, callbacks);
745
+ }
746
+ callbacks.add(cleanup);
747
+ return () => { callbacks.delete(cleanup); };
748
+ }
749
+ function composedParent(node) {
750
+ return node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);
751
+ }
752
+ function isCardDisabled(node) {
753
+ for (let current = node; current; current = composedParent(current)) {
754
+ if (current instanceof HTMLElement && controllers.get(current)?.isDisabled)
755
+ return true;
756
+ }
757
+ return false;
758
+ }
759
+ const BLOCKED_EVENTS = [
760
+ 'click', 'dblclick', 'auxclick', 'contextmenu',
761
+ 'beforeinput', 'input', 'change', 'submit', 'keydown', 'keyup',
762
+ 'compositionstart', 'compositionupdate', 'compositionend',
763
+ 'choice-request-change', 'choice-navigate',
764
+ ];
765
+ const TOUCH_EVENTS = ['pointerdown', 'pointerup', 'touchstart', 'touchend', 'mousedown', 'mouseup'];
766
+ const TAB_TARGETS = [
767
+ 'a[href]', 'area[href]', 'button', 'input', 'select', 'textarea', 'summary',
768
+ 'iframe', 'object', 'embed', 'audio[controls]', 'video[controls]',
769
+ '[tabindex]', '[contenteditable]:not([contenteditable="false"])',
770
+ ].join(',');
771
+ const VISUAL_ATTRIBUTES = ['data-disabled', 'data-copilot-card-disabled'];
772
+ const ownsStyle = (name) => name === 'background' || name.startsWith('background-')
773
+ || name === 'color' || name === 'pointer-events' || name === 'cursor';
774
+ function restoreAttribute(element, name, value) {
775
+ if (value === null)
776
+ element.removeAttribute(name);
777
+ else
778
+ element.setAttribute(name, value);
779
+ }
780
+ function createCardDisabledController(container, initiallyDisabled = false) {
781
+ let disabled = false;
782
+ let disposed = false;
783
+ let epoch = 0;
784
+ let updateDepth = 0;
785
+ let applyingPresentation = false;
786
+ let previousAriaDisabled = null;
787
+ let snapshots = [];
788
+ let tabSnapshots = [];
789
+ let tabObserver;
790
+ const block = (event) => {
791
+ if (!disabled || disposed)
792
+ return;
793
+ event.preventDefault();
794
+ event.stopImmediatePropagation();
795
+ };
796
+ // Prevent component gesture handlers without cancelling the browser's scroll.
797
+ const stopGesture = (event) => {
798
+ if (disabled && !disposed)
799
+ event.stopImmediatePropagation();
800
+ };
801
+ const blockFocus = (event) => {
802
+ if (!disabled || disposed)
803
+ return;
804
+ const target = event.composedPath()[0] ?? event.target;
805
+ if (target instanceof HTMLElement)
806
+ target.blur();
807
+ event.stopImmediatePropagation();
808
+ };
809
+ function closeTransientUI(root) {
810
+ for (const element of root.querySelectorAll('*')) {
811
+ interactionCleanups.get(element)?.forEach(cleanup => cleanup());
812
+ const component = element;
813
+ component.closeTransientUI?.();
814
+ if (element.shadowRoot)
815
+ closeTransientUI(element.shadowRoot);
816
+ }
817
+ }
818
+ function blurCardInput() {
819
+ let active = container.ownerDocument.activeElement;
820
+ while (active?.shadowRoot?.activeElement)
821
+ active = active.shadowRoot.activeElement;
822
+ if (!(active instanceof HTMLElement))
823
+ return;
824
+ for (let node = active; node; node = composedParent(node)) {
825
+ if (node === container) {
826
+ active.blur();
827
+ return;
828
+ }
829
+ }
830
+ }
831
+ function excludeFromTabNavigation(root) {
832
+ const observer = tabObserver ?? (tabObserver = new MutationObserver(() => {
833
+ if (!disabled || disposed || updateDepth > 0)
834
+ return;
835
+ // Components such as streaming Markdown also update their own Shadow DOM.
836
+ restoreTabNavigation();
837
+ excludeFromTabNavigation(container);
838
+ }));
839
+ const targets = [];
840
+ const collect = (element) => {
841
+ if (element.matches(TAB_TARGETS)) {
842
+ targets.push(element);
843
+ return;
844
+ }
845
+ // Browsers can put scroll containers in the Tab order without tabindex.
846
+ const style = element.ownerDocument.defaultView?.getComputedStyle(element);
847
+ if (style && [style.overflowX, style.overflowY].some(value => value === 'auto' || value === 'scroll')) {
848
+ targets.push(element);
849
+ }
850
+ };
851
+ const walk = (parent) => {
852
+ observer.observe(parent, { childList: true, subtree: true });
853
+ if (parent instanceof HTMLElement)
854
+ collect(parent);
855
+ for (const element of parent.querySelectorAll('*')) {
856
+ collect(element);
857
+ if (element.shadowRoot)
858
+ walk(element.shadowRoot);
859
+ }
860
+ };
861
+ // Collect before writing attributes so style reads stay in one batch.
862
+ walk(root);
863
+ for (const element of targets) {
864
+ tabSnapshots.push({ element, tabindex: element.getAttribute('tabindex') });
865
+ element.setAttribute('tabindex', '-1');
866
+ }
867
+ }
868
+ function restoreTabNavigation() {
869
+ tabObserver?.disconnect();
870
+ if (!disabled)
871
+ tabObserver = undefined;
872
+ for (const { element, tabindex } of tabSnapshots) {
873
+ restoreAttribute(element, 'tabindex', tabindex);
874
+ }
875
+ tabSnapshots = [];
876
+ }
877
+ function restorePresentation() {
878
+ restoreTabNavigation();
879
+ for (const { element, attributes, styles } of snapshots) {
880
+ VISUAL_ATTRIBUTES.forEach((name, index) => restoreAttribute(element, name, attributes[index]));
881
+ if (styles) {
882
+ Array.from(element.style).filter(ownsStyle).forEach(name => element.style.removeProperty(name));
883
+ styles.forEach(([name, value, priority]) => element.style.setProperty(name, value, priority));
884
+ }
885
+ }
886
+ snapshots = [];
887
+ }
888
+ function applyPresentation() {
889
+ if (!disabled || disposed || updateDepth > 0 || applyingPresentation)
890
+ return;
891
+ applyingPresentation = true;
892
+ try {
893
+ // Blur/popup callbacks can synchronously update or toggle the card.
894
+ // Always snapshot its current underlying presentation, never our overlay.
895
+ restorePresentation();
896
+ closeTransientUI(container);
897
+ blurCardInput();
898
+ if (!disabled || disposed)
899
+ return;
900
+ for (const element of container.querySelectorAll('*')) {
901
+ const isRoot = element.parentElement === container;
902
+ snapshots.push({
903
+ element,
904
+ attributes: VISUAL_ATTRIBUTES.map(name => element.getAttribute(name)),
905
+ styles: isRoot ? Array.from(element.style).filter(ownsStyle)
906
+ .map(name => [name, element.style.getPropertyValue(name), element.style.getPropertyPriority(name)]) : undefined,
907
+ });
908
+ }
909
+ for (const root of Array.from(container.children)) {
910
+ if (!(root instanceof HTMLElement))
911
+ continue;
912
+ applyDisabledState(root, true);
913
+ applyDisabledDescendants(root, true);
914
+ }
915
+ excludeFromTabNavigation(container);
916
+ }
917
+ finally {
918
+ applyingPresentation = false;
919
+ }
920
+ }
921
+ function listen(enabled) {
922
+ for (const name of BLOCKED_EVENTS) {
923
+ if (enabled)
924
+ container.addEventListener(name, block, true);
925
+ else
926
+ container.removeEventListener(name, block, true);
927
+ }
928
+ for (const name of TOUCH_EVENTS) {
929
+ if (enabled)
930
+ container.addEventListener(name, stopGesture, { capture: true, passive: true });
931
+ else
932
+ container.removeEventListener(name, stopGesture, true);
933
+ }
934
+ if (enabled)
935
+ container.addEventListener('focus', blockFocus, true);
936
+ else
937
+ container.removeEventListener('focus', blockFocus, true);
938
+ }
939
+ const controller = {
940
+ get isDisabled() { return disabled && !disposed; },
941
+ get epoch() { return epoch; },
942
+ setDisabled(value) {
943
+ if (disposed || disabled === value)
944
+ return;
945
+ disabled = value;
946
+ epoch += 1;
947
+ if (disabled) {
948
+ previousAriaDisabled = container.getAttribute('aria-disabled');
949
+ container.setAttribute('aria-disabled', 'true');
950
+ listen(true);
951
+ blurCardInput();
952
+ applyPresentation();
953
+ }
954
+ else {
955
+ restorePresentation();
956
+ restoreAttribute(container, 'aria-disabled', previousAriaDisabled);
957
+ listen(false);
958
+ }
959
+ },
960
+ update(render) {
961
+ if (updateDepth === 0)
962
+ restorePresentation();
963
+ updateDepth += 1;
964
+ try {
965
+ return render();
966
+ }
967
+ finally {
968
+ updateDepth -= 1;
969
+ if (updateDepth === 0)
970
+ applyPresentation();
971
+ }
972
+ },
973
+ dispose() {
974
+ if (disposed)
975
+ return;
976
+ controller.setDisabled(false);
977
+ disposed = true;
978
+ controllers.delete(container);
979
+ },
980
+ };
981
+ controllers.set(container, controller);
982
+ controller.setDisabled(initiallyDisabled);
983
+ return controller;
984
+ }
985
+
716
986
  /**
717
987
  * Slot Layout Renderers — each slot key maps to a specific layout strategy.
718
988
  *
@@ -1033,6 +1303,15 @@ function mergePresentationOwnership(...ownership) {
1033
1303
  styleProperties: Array.from(new Set(ownership.flatMap(value => value.styleProperties))),
1034
1304
  };
1035
1305
  }
1306
+ const datePickerRowPaddingPatches = new WeakMap();
1307
+ /** Suspend an open date picker's row padding around renderer-owned style updates. */
1308
+ function registerDatePickerRowPaddingPatch(row, suspend) {
1309
+ datePickerRowPaddingPatches.set(row, suspend);
1310
+ return () => {
1311
+ if (datePickerRowPaddingPatches.get(row) === suspend)
1312
+ datePickerRowPaddingPatches.delete(row);
1313
+ };
1314
+ }
1036
1315
  /** Patch renderer-owned shell presentation without overwriting layout state. */
1037
1316
  function patchElementShellPresentation(current, previous, desired, ...preservedOwnership) {
1038
1317
  const ownership = mergePresentationOwnership(...preservedOwnership);
@@ -1068,43 +1347,49 @@ function patchElementShellPresentation(current, previous, desired, ...preservedO
1068
1347
  else
1069
1348
  current.removeAttribute(name);
1070
1349
  }
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) {
1350
+ const resumeDatePickerPadding = datePickerRowPaddingPatches.get(current)?.();
1351
+ try {
1352
+ const previousPropertyNames = stylePropertyNames(previous.style);
1353
+ const desiredPropertyNames = stylePropertyNames(desired.style);
1354
+ const propertyNames = new Set([
1355
+ ...previousPropertyNames,
1356
+ ...desiredPropertyNames,
1357
+ ]);
1358
+ const currentPropertyNames = stylePropertyNames(current.style);
1359
+ for (const name of propertyNames) {
1360
+ if (layoutStyleProperties.has(name))
1361
+ continue;
1362
+ const previousHas = previousPropertyNames.has(name);
1363
+ const desiredHas = desiredPropertyNames.has(name);
1364
+ const previousValue = previous.style.getPropertyValue(name);
1365
+ const desiredValue = desired.style.getPropertyValue(name);
1366
+ const previousPriority = previous.style.getPropertyPriority(name);
1367
+ const desiredPriority = desired.style.getPropertyPriority(name);
1368
+ if (previousHas === desiredHas
1369
+ && previousValue === desiredValue
1370
+ && previousPriority === desiredPriority) {
1096
1371
  continue;
1097
1372
  }
1373
+ if (previousHas) {
1374
+ if (!currentPropertyNames.has(name)
1375
+ || current.style.getPropertyValue(name) !== previousValue
1376
+ || current.style.getPropertyPriority(name) !== previousPriority) {
1377
+ continue;
1378
+ }
1379
+ }
1380
+ else if (currentPropertyNames.has(name)) {
1381
+ continue;
1382
+ }
1383
+ if (desiredHas) {
1384
+ current.style.setProperty(name, desiredValue, desiredPriority);
1385
+ }
1386
+ else {
1387
+ current.style.removeProperty(name);
1388
+ }
1098
1389
  }
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
- }
1390
+ }
1391
+ finally {
1392
+ resumeDatePickerPadding?.();
1108
1393
  }
1109
1394
  }
1110
1395
  /** Release slot-owned item listeners within a disposed subtree. */
@@ -1391,15 +1676,29 @@ function renderHorizontalScrollSlot(container, children, slotContent, renderChil
1391
1676
  e.preventDefault();
1392
1677
  track.scrollLeft = scrollLeft - (e.pageX - startX);
1393
1678
  };
1394
- const stopDrag = () => {
1679
+ const cancelDrag = () => {
1395
1680
  if (!isDragging)
1396
1681
  return;
1397
1682
  isDragging = false;
1398
1683
  track.style.cursor = 'grab';
1399
1684
  track.style.removeProperty('user-select');
1685
+ };
1686
+ const stopDrag = () => {
1687
+ if (!isDragging)
1688
+ return;
1689
+ cancelDrag();
1400
1690
  if (snap)
1401
1691
  track.style.scrollSnapType = 'x mandatory';
1402
1692
  };
1693
+ // Leave snap suspended on disable so the viewport does not jump. The next
1694
+ // normal drag/wheel completion restores it.
1695
+ ownerCleanup.add(registerCardDisableCleanup(track, () => {
1696
+ cancelDrag();
1697
+ if (wheelTimer) {
1698
+ window.clearTimeout(wheelTimer);
1699
+ wheelTimer = 0;
1700
+ }
1701
+ }));
1403
1702
  addSlotOwnerListener(ownerCleanup, track, 'mousedown', handleMouseDown);
1404
1703
  addSlotOwnerListener(ownerCleanup, track, 'mousemove', handleMouseMove);
1405
1704
  addSlotOwnerListener(ownerCleanup, track, 'mouseup', stopDrag);
@@ -1963,6 +2262,7 @@ function renderCarouselSlot(container, children, _slotContent, props, renderChil
1963
2262
  let isDragging = false;
1964
2263
  let startX = 0;
1965
2264
  let scrollStart = 0;
2265
+ let wheelTimer = 0;
1966
2266
  const handleMouseDown = (event) => {
1967
2267
  if (ownerDisposed)
1968
2268
  return;
@@ -1993,12 +2293,46 @@ function renderCarouselSlot(container, children, _slotContent, props, renderChil
1993
2293
  if (!ownerDisposed)
1994
2294
  snapToNearest();
1995
2295
  };
2296
+ ownerCleanup.add(registerCardDisableCleanup(container, () => {
2297
+ if (isDragging) {
2298
+ isDragging = false;
2299
+ container.style.cursor = 'grab';
2300
+ }
2301
+ if (wheelTimer) {
2302
+ window.clearTimeout(wheelTimer);
2303
+ wheelTimer = 0;
2304
+ }
2305
+ const wasSnapping = pendingSnapScrollEnd !== null || pendingSnapTimer !== 0;
2306
+ clearPendingSnap();
2307
+ // Cancel the browser's in-flight animation as well as its completion callbacks.
2308
+ // The next normal interaction restores snap through snapToNearest.
2309
+ if (wasSnapping) {
2310
+ const left = container.scrollLeft;
2311
+ const overflowX = container.style.getPropertyValue('overflow-x');
2312
+ const priority = container.style.getPropertyPriority('overflow-x');
2313
+ // Safari can deliver a stale compositor frame after scrollTo('instant').
2314
+ // Flush the scroll-container change synchronously so no delayed correction
2315
+ // can overwrite a fresh gesture or API navigation after re-enabling.
2316
+ container.style.setProperty('overflow-x', 'hidden', 'important');
2317
+ try {
2318
+ void container.offsetWidth;
2319
+ container.scrollTo({ left, behavior: 'instant' });
2320
+ }
2321
+ finally {
2322
+ if (overflowX)
2323
+ container.style.setProperty('overflow-x', overflowX, priority);
2324
+ else
2325
+ container.style.removeProperty('overflow-x');
2326
+ }
2327
+ void container.offsetWidth;
2328
+ container.scrollTo({ left, behavior: 'instant' });
2329
+ }
2330
+ }));
1996
2331
  addSlotOwnerListener(ownerCleanup, container, 'mousedown', handleMouseDown);
1997
2332
  addSlotOwnerListener(ownerCleanup, document, 'mousemove', handleMouseMove);
1998
2333
  addSlotOwnerListener(ownerCleanup, document, 'mouseup', handleMouseUp);
1999
2334
  addSlotOwnerListener(ownerCleanup, container, 'touchend', handleTouchEnd, { passive: true });
2000
2335
  // Wheel support
2001
- let wheelTimer = 0;
2002
2336
  const handleWheel = (event) => {
2003
2337
  if (ownerDisposed)
2004
2338
  return;
@@ -2396,6 +2730,8 @@ class BaseElement extends HTMLElement {
2396
2730
  this._responsive = createResponsiveContext(this._isMobile, this._responsiveOptions);
2397
2731
  this.render();
2398
2732
  }
2733
+ /** @internal Close temporary interaction UI without clearing component values. */
2734
+ closeTransientUI() { }
2399
2735
  // ─── Helpers ────────────────────────────────────────────────
2400
2736
  /** Convert any content value to a safe display string. */
2401
2737
  resolveContent(content) {
@@ -3856,7 +4192,8 @@ class CardInput extends BaseElement {
3856
4192
  requestAutoFocus() {
3857
4193
  if (!this.isConnected
3858
4194
  || !this._props.autoFocus
3859
- || this.hasAttribute('data-disabled')) {
4195
+ || this.hasAttribute('data-disabled')
4196
+ || isCardDisabled(this)) {
3860
4197
  return false;
3861
4198
  }
3862
4199
  const control = this.shadowRoot?.querySelector('.card-input');
@@ -4182,6 +4519,10 @@ class CardImage extends BaseElement {
4182
4519
  document.body.style.overflow = '';
4183
4520
  document.removeEventListener('keydown', this._handleEscape);
4184
4521
  }
4522
+ closeTransientUI() {
4523
+ if (this._lightbox?.classList.contains('open'))
4524
+ this.closeLightbox(this._lightbox);
4525
+ }
4185
4526
  disconnectedCallback() {
4186
4527
  // Ensure body overflow is restored when element is removed
4187
4528
  document.body.style.overflow = '';
@@ -5118,6 +5459,9 @@ class CardSelect extends BaseElement {
5118
5459
  });
5119
5460
  });
5120
5461
  }
5462
+ closeTransientUI() {
5463
+ this._setOpen(false);
5464
+ }
5121
5465
  disconnectedCallback() {
5122
5466
  document.removeEventListener('click', this._onDocClick);
5123
5467
  }
@@ -7222,17 +7566,85 @@ const CHEVRON_ICON = `
7222
7566
  <svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
7223
7567
  <path d="M2.5 4.5L6 8l3.5-3.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
7224
7568
  </svg>`;
7569
+ // Opening another picker starts before the document click closes the first one.
7570
+ // Share the row reservation so that this overlap cannot accumulate padding.
7571
+ const mobilePanelRows = new WeakMap();
7572
+ function updateRowSpace(row, space) {
7573
+ // Preserve host style changes made while the calendar owns extra padding.
7574
+ const padding = row.style.getPropertyValue('padding-bottom');
7575
+ const priority = row.style.getPropertyPriority('padding-bottom');
7576
+ if (padding !== space.appliedPaddingBottom || priority !== space.priority) {
7577
+ space.paddingBottom = padding;
7578
+ space.priority = priority;
7579
+ space.basePaddingBottom = padding || getComputedStyle(row).paddingBottom;
7580
+ }
7581
+ if (space.panels.size) {
7582
+ const height = Math.max(...space.panels.values());
7583
+ row.style.setProperty('padding-bottom', `calc(${space.basePaddingBottom} + ${height}px)`, space.priority);
7584
+ }
7585
+ else {
7586
+ if (space.paddingBottom)
7587
+ row.style.setProperty('padding-bottom', space.paddingBottom, space.priority);
7588
+ else
7589
+ row.style.removeProperty('padding-bottom');
7590
+ mobilePanelRows.delete(row);
7591
+ space.releasePatch();
7592
+ }
7593
+ // CSSOM can simplify calc(); compare its serialized value on the next write.
7594
+ space.appliedPaddingBottom = row.style.getPropertyValue('padding-bottom');
7595
+ }
7225
7596
  class CardDatePicker extends BaseElement {
7226
7597
  constructor() {
7227
7598
  super(...arguments);
7228
7599
  this._open = false;
7229
7600
  this._calendar = null;
7601
+ this._panelResizeObserver = null;
7602
+ this._mobilePanelRow = null;
7230
7603
  /** Dates picked in the panel; shown until props.value changes externally. */
7231
7604
  this._localValue = null;
7232
7605
  this._onDocClick = (e) => {
7233
- if (!this.contains(e.target))
7606
+ if (!e.composedPath().includes(this))
7234
7607
  this._setOpen(false);
7235
7608
  };
7609
+ this._positionMobilePanel = () => {
7610
+ if (!this._isMobile || !this._open)
7611
+ return;
7612
+ const panel = this.shadowRoot?.querySelector('.panel');
7613
+ const calendar = panel?.querySelector('[data-vc="calendar"]');
7614
+ if (!panel || !calendar)
7615
+ return;
7616
+ this._panelResizeObserver?.observe(calendar);
7617
+ const row = this._mobilePanelRow;
7618
+ if (row) {
7619
+ const style = getComputedStyle(row);
7620
+ const contentBottom = row.getBoundingClientRect().bottom
7621
+ - parseFloat(style.paddingBottom) - parseFloat(style.borderBottomWidth);
7622
+ panel.style.top = `${contentBottom - this.getBoundingClientRect().top}px`;
7623
+ const space = mobilePanelRows.get(row);
7624
+ space.panels.set(this, calendar.getBoundingClientRect().bottom - contentBottom);
7625
+ updateRowSpace(row, space);
7626
+ }
7627
+ panel.style.left = '0px';
7628
+ const bounds = calendar.getBoundingClientRect();
7629
+ let left = 0;
7630
+ let right = document.documentElement.clientWidth;
7631
+ // Intersect clipping ancestors, crossing shadow roots when cards are embedded.
7632
+ let ancestor = this.shadowRoot.host;
7633
+ while (ancestor) {
7634
+ this._panelResizeObserver?.observe(ancestor);
7635
+ if (/^(auto|scroll|hidden|clip)$/.test(getComputedStyle(ancestor).overflowX)) {
7636
+ const rect = ancestor.getBoundingClientRect();
7637
+ const innerLeft = rect.left + ancestor.clientLeft;
7638
+ left = Math.max(left, innerLeft);
7639
+ right = Math.min(right, innerLeft + ancestor.clientWidth);
7640
+ }
7641
+ const root = ancestor.getRootNode();
7642
+ ancestor = ancestor.assignedSlot ?? ancestor.parentElement
7643
+ ?? (root instanceof ShadowRoot ? root.host : null);
7644
+ }
7645
+ // Shift the panel without changing the trigger's intrinsic width.
7646
+ panel.style.left = `${Math.max(left, Math.min(bounds.left, right - bounds.width)) - bounds.left}px`;
7647
+ };
7236
7648
  }
7237
7649
  render() {
7238
7650
  if (!this.shadowRoot || !this._node)
@@ -7333,8 +7745,11 @@ class CardDatePicker extends BaseElement {
7333
7745
  .trigger-arrow.open { transform: rotate(180deg); }
7334
7746
  .panel {
7335
7747
  display: none;
7336
- position: absolute;
7337
- top: calc(100% + 4px);
7748
+ /* Column layouts include the calendar in flow. Horizontal flex rows
7749
+ reserve space below the whole row when the panel is mounted. */
7750
+ position: ${this._isMobile ? 'relative' : 'absolute'};
7751
+ ${this._isMobile ? 'margin-top: 4px; contain: inline-size;' : ''}
7752
+ top: ${this._isMobile ? '0' : 'calc(100% + 4px)'};
7338
7753
  left: 0;
7339
7754
  z-index: 1000;
7340
7755
  }
@@ -7386,6 +7801,9 @@ class CardDatePicker extends BaseElement {
7386
7801
  this._destroyCalendar();
7387
7802
  }
7388
7803
  // ─── Private ────────────────────────────────────────────────
7804
+ closeTransientUI() {
7805
+ this._setOpen(false);
7806
+ }
7389
7807
  _setOpen(open) {
7390
7808
  if (this._open === open)
7391
7809
  return;
@@ -7467,8 +7885,59 @@ class CardDatePicker extends BaseElement {
7467
7885
  },
7468
7886
  });
7469
7887
  this._calendar.init();
7888
+ if (this._isMobile) {
7889
+ const row = this.parentElement;
7890
+ if (row && /^(inline-)?flex$/.test(getComputedStyle(row).display)
7891
+ && /^row/.test(getComputedStyle(row).flexDirection)) {
7892
+ const panel = container.parentElement;
7893
+ panel.style.position = 'absolute';
7894
+ this._mobilePanelRow = row;
7895
+ if (!mobilePanelRows.has(row)) {
7896
+ const space = {
7897
+ paddingBottom: row.style.getPropertyValue('padding-bottom'),
7898
+ priority: row.style.getPropertyPriority('padding-bottom'),
7899
+ basePaddingBottom: row.style.paddingBottom || getComputedStyle(row).paddingBottom,
7900
+ appliedPaddingBottom: row.style.getPropertyValue('padding-bottom'),
7901
+ panels: new Map(),
7902
+ releasePatch: () => { },
7903
+ };
7904
+ mobilePanelRows.set(row, space);
7905
+ space.releasePatch = registerDatePickerRowPaddingPatch(row, () => {
7906
+ // Make the schema's baseline visible to the incremental style diff.
7907
+ // If a host already replaced our value, leave that external style.
7908
+ if (row.style.getPropertyValue('padding-bottom') === space.appliedPaddingBottom
7909
+ && row.style.getPropertyPriority('padding-bottom') === space.priority) {
7910
+ if (space.paddingBottom)
7911
+ row.style.setProperty('padding-bottom', space.paddingBottom, space.priority);
7912
+ else
7913
+ row.style.removeProperty('padding-bottom');
7914
+ space.appliedPaddingBottom = row.style.getPropertyValue('padding-bottom');
7915
+ }
7916
+ return () => updateRowSpace(row, space);
7917
+ });
7918
+ }
7919
+ }
7920
+ if (typeof ResizeObserver !== 'undefined') {
7921
+ this._panelResizeObserver = new ResizeObserver(this._positionMobilePanel);
7922
+ }
7923
+ // Calendar navigation changes height synchronously, including browsers
7924
+ // without ResizeObserver. Run after the calendar's own click handler.
7925
+ container.parentElement.addEventListener('click', this._positionMobilePanel);
7926
+ window.addEventListener('resize', this._positionMobilePanel);
7927
+ this._positionMobilePanel();
7928
+ }
7470
7929
  }
7471
7930
  _destroyCalendar() {
7931
+ window.removeEventListener('resize', this._positionMobilePanel);
7932
+ this.shadowRoot?.querySelector('.panel')?.removeEventListener('click', this._positionMobilePanel);
7933
+ this._panelResizeObserver?.disconnect();
7934
+ this._panelResizeObserver = null;
7935
+ if (this._mobilePanelRow) {
7936
+ const space = mobilePanelRows.get(this._mobilePanelRow);
7937
+ space.panels.delete(this);
7938
+ updateRowSpace(this._mobilePanelRow, space);
7939
+ this._mobilePanelRow = null;
7940
+ }
7472
7941
  if (this._calendar) {
7473
7942
  this._calendar.destroy();
7474
7943
  this._calendar = null;
@@ -8972,7 +9441,7 @@ function createCardNodeAccess(container) {
8972
9441
  },
8973
9442
  focus(options) {
8974
9443
  const element = resolveElement(id);
8975
- if (!element || element.hasAttribute('data-disabled'))
9444
+ if (!element || element.hasAttribute('data-disabled') || isCardDisabled(element))
8976
9445
  return false;
8977
9446
  const target = element.shadowRoot
8978
9447
  ?.querySelector(FOCUS_TARGET_SELECTOR) ?? element;
@@ -9192,40 +9661,6 @@ function createCardNodeAccess(container) {
9192
9661
  };
9193
9662
  }
9194
9663
 
9195
- 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
- function applyDisabledState(element, disabled) {
9213
- if (!disabled)
9214
- return;
9215
- ensureDisabledPresentationStyles(element.ownerDocument);
9216
- element.setAttribute('data-disabled', 'true');
9217
- element.setAttribute(DISABLED_MARKER, 'true');
9218
- element.style.pointerEvents = 'none';
9219
- element.style.cursor = 'default';
9220
- }
9221
- function applyDisabledDescendants(element, disabled) {
9222
- if (!disabled)
9223
- return;
9224
- element.querySelectorAll('*').forEach((child) => {
9225
- child.setAttribute('data-disabled', 'true');
9226
- });
9227
- }
9228
-
9229
9664
  const FORCE_REPLACE = Symbol('bound-force-replace');
9230
9665
  const REPEAT_APPEND = Symbol('bound-repeat-append');
9231
9666
  /**
@@ -9479,6 +9914,7 @@ function renderBoundCard(container, schema, options) {
9479
9914
  },
9480
9915
  events,
9481
9916
  isActive: () => (!disposed
9917
+ && !cardDisabled.isDisabled
9482
9918
  && !publishing
9483
9919
  && container.contains(element)),
9484
9920
  });
@@ -9982,7 +10418,12 @@ function renderBoundCard(container, schema, options) {
9982
10418
  try {
9983
10419
  for (const [occurrenceKey, plan] of appendPlans) {
9984
10420
  const prepared = findPreparedByOccurrenceKey(preparedRoot, occurrenceKey);
9985
- if (!prepared?.[REPEAT_APPEND]) {
10421
+ // Hidden or force-replaced ancestors prune their descendants from the
10422
+ // prepared tree. Their normal replacement commits the new variables;
10423
+ // there is no live descendant append to prepare in this transaction.
10424
+ if (!prepared)
10425
+ continue;
10426
+ if (!prepared[REPEAT_APPEND]) {
9986
10427
  throw new Error(`[renderCard] Missing prepared repeat append owner "${occurrenceKey}"`);
9987
10428
  }
9988
10429
  const nextOccurrenceKeys = occurrenceKeysForChildren(plan.current.occurrenceKey, plan.nextNode.children);
@@ -10463,7 +10904,7 @@ function renderBoundCard(container, schema, options) {
10463
10904
  error.failures = failures;
10464
10905
  return error;
10465
10906
  }
10466
- function commitIncrementalCandidate(candidate, renderVariables) {
10907
+ function commitIncrementalCandidateContent(candidate, renderVariables) {
10467
10908
  const replacements = candidate.replacementRoots;
10468
10909
  try {
10469
10910
  if (!currentMountedTree) {
@@ -10655,6 +11096,9 @@ function renderBoundCard(container, schema, options) {
10655
11096
  disposeCandidate(candidate);
10656
11097
  }
10657
11098
  }
11099
+ function commitIncrementalCandidate(candidate, renderVariables) {
11100
+ return cardDisabled.update(() => commitIncrementalCandidateContent(candidate, renderVariables));
11101
+ }
10658
11102
  function assertCurrentRevision(baseRevision) {
10659
11103
  if (disposed || publishing || revision !== baseRevision) {
10660
11104
  const error = new Error('[renderCard] BOUND_TRANSACTION_CONFLICT');
@@ -10662,7 +11106,7 @@ function renderBoundCard(container, schema, options) {
10662
11106
  throw error;
10663
11107
  }
10664
11108
  }
10665
- function commitDraft(before, draft, baseRevision) {
11109
+ function commitDraftContent(before, draft, baseRevision) {
10666
11110
  assertCurrentRevision(baseRevision);
10667
11111
  const validationRoot = cloneJsonData(variables);
10668
11112
  replaceRootContents(validationRoot, draft);
@@ -10716,6 +11160,9 @@ function renderBoundCard(container, schema, options) {
10716
11160
  reconcileLifecycles(selectLifecycleNodes(currentMaterialized, collectMountedLifecycleIds(currentMountedTree)));
10717
11161
  applyAutoFocus(candidate);
10718
11162
  }
11163
+ function commitDraft(before, draft, baseRevision) {
11164
+ return cardDisabled.update(() => commitDraftContent(before, draft, baseRevision));
11165
+ }
10719
11166
  function writeDraftVariable(draft, key, value) {
10720
11167
  Object.defineProperty(draft, String(key), {
10721
11168
  value,
@@ -10772,7 +11219,7 @@ function renderBoundCard(container, schema, options) {
10772
11219
  };
10773
11220
  }
10774
11221
  async function runBoundEvent(runtimeId, eventName, eventDetail, baseRevision, repeatTargetFingerprint) {
10775
- if (disposed)
11222
+ if (disposed || cardDisabled.isDisabled)
10776
11223
  return;
10777
11224
  assertCurrentRevision(baseRevision);
10778
11225
  const transactionBefore = cloneJsonData(variables);
@@ -10808,13 +11255,14 @@ function renderBoundCard(container, schema, options) {
10808
11255
  commitDraft(cloneJsonData(lastPublishedVariables), draft, baseRevision);
10809
11256
  }
10810
11257
  function enqueueBoundEvent(runtimeId, eventName, eventDetail, repeatTargetFingerprint) {
10811
- if (disposed)
11258
+ if (disposed || cardDisabled.isDisabled)
10812
11259
  return;
11260
+ const interactionEpoch = cardDisabled.epoch;
10813
11261
  const enqueueRevision = revision;
10814
11262
  const queuedBehindAnotherAction = queuedActionCount > 0;
10815
11263
  queuedActionCount += 1;
10816
11264
  actionQueue = actionQueue
10817
- .then(() => runBoundEvent(runtimeId, eventName, eventDetail, queuedBehindAnotherAction ? revision : enqueueRevision, repeatTargetFingerprint))
11265
+ .then(() => interactionEpoch === cardDisabled.epoch ? runBoundEvent(runtimeId, eventName, eventDetail, queuedBehindAnotherAction ? revision : enqueueRevision, repeatTargetFingerprint) : undefined)
10818
11266
  .catch((error) => {
10819
11267
  if (error
10820
11268
  && typeof error === 'object'
@@ -10846,6 +11294,7 @@ function renderBoundCard(container, schema, options) {
10846
11294
  disposed = true;
10847
11295
  disposeRequested = false;
10848
11296
  nodeAccess.dispose();
11297
+ cardDisabled.dispose();
10849
11298
  abortController.abort();
10850
11299
  if (currentMountedTree) {
10851
11300
  disposeBoundSubtree(currentMountedTree);
@@ -10883,17 +11332,26 @@ function renderBoundCard(container, schema, options) {
10883
11332
  console.error('[renderCard] Bound lifecycle dispose failed', error);
10884
11333
  });
10885
11334
  }
10886
- const initialCandidate = prepareIncrementalCandidate(variables, variables);
10887
- publishing = true;
11335
+ const cardDisabled = createCardDisabledController(container, options.disabled);
10888
11336
  try {
10889
- currentMountedTree = commitIncrementalCandidate(initialCandidate, variables);
10890
- currentMaterialized = materializeCard(schema, variables);
11337
+ const initialCandidate = prepareIncrementalCandidate(variables, variables);
11338
+ publishing = true;
11339
+ try {
11340
+ currentMountedTree = commitIncrementalCandidate(initialCandidate, variables);
11341
+ currentMaterialized = materializeCard(schema, variables);
11342
+ }
11343
+ finally {
11344
+ publishing = false;
11345
+ }
11346
+ reconcileLifecycles(selectLifecycleNodes(currentMaterialized, collectMountedLifecycleIds(currentMountedTree)));
10891
11347
  }
10892
- finally {
10893
- publishing = false;
11348
+ catch (error) {
11349
+ nodeAccess.dispose();
11350
+ cardDisabled.dispose();
11351
+ throw error;
10894
11352
  }
10895
- reconcileLifecycles(selectLifecycleNodes(currentMaterialized, collectMountedLifecycleIds(currentMountedTree)));
10896
11353
  return {
11354
+ setDisabled: cardDisabled.setDisabled,
10897
11355
  getNode: nodeAccess.getNode,
10898
11356
  onFocusChange: nodeAccess.onFocusChange,
10899
11357
  onScroll: nodeAccess.onScroll,
@@ -11261,7 +11719,7 @@ function renderStaticCard(container, schema, options) {
11261
11719
  };
11262
11720
  }
11263
11721
  function runStaticEventActions(steps, detail) {
11264
- if (disposed)
11722
+ if (disposed || cardDisabled.isDisabled)
11265
11723
  return;
11266
11724
  if (detail != null)
11267
11725
  variables._event = detail;
@@ -11400,6 +11858,7 @@ function renderStaticCard(container, schema, options) {
11400
11858
  },
11401
11859
  events: resolvedEventBindings,
11402
11860
  isActive: () => (!disposed
11861
+ && !cardDisabled.isDisabled
11403
11862
  && !publishing
11404
11863
  && container.contains(element)),
11405
11864
  });
@@ -11598,7 +12057,7 @@ function renderStaticCard(container, schema, options) {
11598
12057
  }
11599
12058
  }
11600
12059
  }
11601
- function render() {
12060
+ function renderContent() {
11602
12061
  if (disposed)
11603
12062
  return;
11604
12063
  const generation = ++renderGeneration;
@@ -11692,6 +12151,9 @@ function renderStaticCard(container, schema, options) {
11692
12151
  startLifecycleMount(id, lifecycle);
11693
12152
  }
11694
12153
  }
12154
+ function render() {
12155
+ return cardDisabled.update(() => renderContent());
12156
+ }
11695
12157
  function rerender() {
11696
12158
  if (disposed)
11697
12159
  return;
@@ -11705,6 +12167,7 @@ function renderStaticCard(container, schema, options) {
11705
12167
  disposeRequested = false;
11706
12168
  hostAuthorityEpoch += 1;
11707
12169
  nodeAccess.dispose();
12170
+ cardDisabled.dispose();
11708
12171
  abortController.abort();
11709
12172
  if (mountedTree) {
11710
12173
  disposeStaticSubtree(mountedTree);
@@ -11718,9 +12181,18 @@ function renderStaticCard(container, schema, options) {
11718
12181
  destroyLifecycle(record);
11719
12182
  }
11720
12183
  // 7. Initial render
11721
- render();
12184
+ const cardDisabled = createCardDisabledController(container, options.disabled);
12185
+ try {
12186
+ render();
12187
+ }
12188
+ catch (error) {
12189
+ nodeAccess.dispose();
12190
+ cardDisabled.dispose();
12191
+ throw error;
12192
+ }
11722
12193
  // 8. Return instance handle
11723
12194
  return {
12195
+ setDisabled: cardDisabled.setDisabled,
11724
12196
  getNode: nodeAccess.getNode,
11725
12197
  onFocusChange: nodeAccess.onFocusChange,
11726
12198
  onScroll: nodeAccess.onScroll,
@@ -12101,6 +12573,8 @@ function renderStreamingCard(container, options = {}) {
12101
12573
  const variableKey = resolvedProps.variableKey;
12102
12574
  if (variableKey) {
12103
12575
  element.addEventListener('input', ((event) => {
12576
+ if (disposed || cardDisabled.isDisabled || !container.contains(element))
12577
+ return;
12104
12578
  if (event.target !== element || !currentMaterialized)
12105
12579
  return;
12106
12580
  const value = event.detail?.value
@@ -12116,6 +12590,8 @@ function renderStreamingCard(container, options = {}) {
12116
12590
  }
12117
12591
  const domEvent = eventMap[eventName] ?? eventName;
12118
12592
  element.addEventListener(domEvent, ((event) => {
12593
+ if (disposed || cardDisabled.isDisabled || !container.contains(element))
12594
+ return;
12119
12595
  const ownsValueEvent = ((domEvent === 'input' || domEvent === 'change')
12120
12596
  && valueControlTypes.has(node.type));
12121
12597
  if (ownsValueEvent && event.target !== element)
@@ -12184,6 +12660,8 @@ function renderStreamingCard(container, options = {}) {
12184
12660
  const variableKey = resolvedProps.variableKey;
12185
12661
  if (variableKey) {
12186
12662
  el.addEventListener('input', ((e) => {
12663
+ if (disposed || cardDisabled.isDisabled || !container.contains(el))
12664
+ return;
12187
12665
  const value = e.detail?.value
12188
12666
  ?? e.target?.value;
12189
12667
  if (value !== undefined) {
@@ -12202,6 +12680,8 @@ function renderStreamingCard(container, options = {}) {
12202
12680
  continue;
12203
12681
  const domEvent = eventMap[event] ?? event;
12204
12682
  el.addEventListener(domEvent, ((e) => {
12683
+ if (disposed || cardDisabled.isDisabled || !container.contains(el))
12684
+ return;
12205
12685
  if (e instanceof CustomEvent && e.detail != null) {
12206
12686
  variables._event = e.detail;
12207
12687
  }
@@ -12385,7 +12865,7 @@ function renderStreamingCard(container, options = {}) {
12385
12865
  * (visibility flips / container shape changes) re-render only their subtree.
12386
12866
  * Falls back to a full render solely when a subtree can't be rebuilt.
12387
12867
  */
12388
- function diffAllElements(previousVariables) {
12868
+ function diffAllElementsContent(previousVariables) {
12389
12869
  actionContext = buildActionContext();
12390
12870
  const schema = (currentSurfaceId ? engine.getSchema(currentSurfaceId) : undefined) ?? currentSchema;
12391
12871
  if (!schema)
@@ -12425,6 +12905,8 @@ function renderStreamingCard(container, options = {}) {
12425
12905
  continue;
12426
12906
  if ('updateProps' in el) {
12427
12907
  el.updateProps(resolved, isMobile, options.responsive);
12908
+ // Some components replace host cssText while updating their props.
12909
+ applyDisabledState(el, wasDisabled);
12428
12910
  propsCache.set(id, key);
12429
12911
  }
12430
12912
  else if (!replaceSubtree(schema, id, el)) {
@@ -12435,6 +12917,9 @@ function renderStreamingCard(container, options = {}) {
12435
12917
  }
12436
12918
  applyAutoFocusIds(autoFocusIds);
12437
12919
  }
12920
+ function diffAllElements(previousVariables) {
12921
+ return cardDisabled.update(() => diffAllElementsContent(previousVariables));
12922
+ }
12438
12923
  function captureBoundScrollPositions(root) {
12439
12924
  const positions = new Map();
12440
12925
  root.querySelectorAll('[data-scroll-id]').forEach((element) => {
@@ -12785,7 +13270,7 @@ function renderStreamingCard(container, options = {}) {
12785
13270
  throw error;
12786
13271
  }
12787
13272
  }
12788
- function commitBoundFull(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
13273
+ function commitBoundFullContent(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
12789
13274
  assertBoundRevision(baseRevision);
12790
13275
  commitAuthoritativeVariables?.();
12791
13276
  replaceRootContents(variables, draft);
@@ -12807,6 +13292,9 @@ function renderStreamingCard(container, options = {}) {
12807
13292
  restoreBoundScrollPositions(container, scrollPositions);
12808
13293
  restoreBoundMediaStates(container, mediaStates);
12809
13294
  }
13295
+ function commitBoundFull(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
13296
+ return cardDisabled.update(() => commitBoundFullContent(prepared, schema, draft, baseRevision, commitAuthoritativeVariables));
13297
+ }
12810
13298
  function ownerKeysWithoutNestedDuplicates(materialized, owners) {
12811
13299
  const selected = new Set(owners.map(owner => owner.key));
12812
13300
  return owners.filter((owner) => {
@@ -12926,7 +13414,7 @@ function renderStreamingCard(container, options = {}) {
12926
13414
  replacedLifecycleIds,
12927
13415
  };
12928
13416
  }
12929
- function commitBoundIncremental(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
13417
+ function commitBoundIncrementalContent(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
12930
13418
  assertBoundRevision(baseRevision);
12931
13419
  commitAuthoritativeVariables?.();
12932
13420
  replaceRootContents(variables, draft);
@@ -12957,6 +13445,9 @@ function renderStreamingCard(container, options = {}) {
12957
13445
  restoreBoundScrollPositions(container, scrollPositions);
12958
13446
  restoreBoundMediaStates(container, mediaStates);
12959
13447
  }
13448
+ function commitBoundIncremental(prepared, schema, draft, baseRevision, commitAuthoritativeVariables) {
13449
+ return cardDisabled.update(() => commitBoundIncrementalContent(prepared, schema, draft, baseRevision, commitAuthoritativeVariables));
13450
+ }
12960
13451
  function prepareAndCommitBoundUpdate(schema, draft, owners, baseRevision, commitAuthoritativeVariables, changedSourceIds = new Set()) {
12961
13452
  try {
12962
13453
  const prepared = prepareBoundIncremental(schema, draft, owners, changedSourceIds);
@@ -13136,6 +13627,8 @@ function renderStreamingCard(container, options = {}) {
13136
13627
  prepareAndCommitBoundUpdate(schema, draft, owners, baseRevision, commitAuthoritative);
13137
13628
  }
13138
13629
  async function runBoundEvent(runtimeId, eventName, eventDetail) {
13630
+ if (disposed || cardDisabled.isDisabled)
13631
+ return;
13139
13632
  if (!currentMaterialized || !currentSchema || !currentSurfaceId)
13140
13633
  return;
13141
13634
  const baseRevision = boundRevision;
@@ -13162,8 +13655,12 @@ function renderStreamingCard(container, options = {}) {
13162
13655
  applyAutoFocusIds(autoFocusIds);
13163
13656
  }
13164
13657
  function enqueueBoundEvent(runtimeId, eventName, eventDetail) {
13658
+ if (disposed || cardDisabled.isDisabled)
13659
+ return;
13660
+ const interactionEpoch = cardDisabled.epoch;
13165
13661
  boundActionQueue = boundActionQueue
13166
- .then(() => runBoundEvent(runtimeId, eventName, eventDetail))
13662
+ .then(() => interactionEpoch === cardDisabled.epoch
13663
+ ? runBoundEvent(runtimeId, eventName, eventDetail) : undefined)
13167
13664
  .catch((error) => {
13168
13665
  if (error
13169
13666
  && typeof error === 'object'
@@ -13338,7 +13835,7 @@ function renderStreamingCard(container, options = {}) {
13338
13835
  * assigned on createSurface — so newly added components would not render and text
13339
13836
  * appended via appendContent would be wiped on the next full re-render.
13340
13837
  */
13341
- function renderFull() {
13838
+ function renderFullContent() {
13342
13839
  const engineSchema = currentSurfaceId ? engine.getSchema(currentSurfaceId) : undefined;
13343
13840
  const schema = engineSchema ?? currentSchema;
13344
13841
  if (!schema)
@@ -13365,6 +13862,9 @@ function renderStreamingCard(container, options = {}) {
13365
13862
  const dom = renderNode(tree);
13366
13863
  container.appendChild(dom);
13367
13864
  }
13865
+ function renderFull() {
13866
+ return cardDisabled.update(() => renderFullContent());
13867
+ }
13368
13868
  /**
13369
13869
  * Full render that tolerates mid-stream schemas (e.g. children referencing
13370
13870
  * elements that have not arrived yet): keeps the previous DOM on failure so
@@ -13381,7 +13881,7 @@ function renderStreamingCard(container, options = {}) {
13381
13881
  }
13382
13882
  }
13383
13883
  // ─── Engine Event Handlers ──────────────────────────────────────
13384
- const engine = new StreamingEngine({
13884
+ const engineEvents = {
13385
13885
  onSurfaceCreated(surfaceId, schemaInput) {
13386
13886
  if (schemaInput) {
13387
13887
  const nextSchema = engine.getSchema(surfaceId)
@@ -13596,6 +14096,7 @@ function renderStreamingCard(container, options = {}) {
13596
14096
  const typeChanged = el.getAttribute('data-card-type') !== element.type;
13597
14097
  if (!typeChanged && 'updateProps' in el) {
13598
14098
  el.updateProps(resolved, isMobile, options.responsive);
14099
+ applyDisabledState(el, wasDisabled);
13599
14100
  propsCache.set(id, key);
13600
14101
  }
13601
14102
  else if (!typeChanged && isChildrenOnlyChange(propsCache.get(id), key, element)) {
@@ -13746,9 +14247,18 @@ function renderStreamingCard(container, options = {}) {
13746
14247
  boundRevision += 1;
13747
14248
  lifecycleManager.dispose(actionContext);
13748
14249
  },
14250
+ };
14251
+ const engine = new StreamingEngine({
14252
+ onSurfaceCreated: (...args) => cardDisabled.update(() => engineEvents.onSurfaceCreated?.(...args)),
14253
+ onComponentsUpdated: (...args) => cardDisabled.update(() => engineEvents.onComponentsUpdated?.(...args)),
14254
+ onDataModelUpdated: (...args) => cardDisabled.update(() => engineEvents.onDataModelUpdated?.(...args)),
14255
+ onContentAppended: (...args) => cardDisabled.update(() => engineEvents.onContentAppended?.(...args)),
14256
+ onSurfaceDeleted: (...args) => cardDisabled.update(() => engineEvents.onSurfaceDeleted?.(...args)),
13749
14257
  });
13750
14258
  // ─── Public API ─────────────────────────────────────────────────
14259
+ const cardDisabled = createCardDisabledController(container, options.disabled);
13751
14260
  return {
14261
+ setDisabled: cardDisabled.setDisabled,
13752
14262
  getNode: nodeAccess.getNode,
13753
14263
  onFocusChange: nodeAccess.onFocusChange,
13754
14264
  onScroll: nodeAccess.onScroll,
@@ -13829,6 +14339,7 @@ function renderStreamingCard(container, options = {}) {
13829
14339
  return;
13830
14340
  disposed = true;
13831
14341
  nodeAccess.dispose();
14342
+ cardDisabled.dispose();
13832
14343
  boundRevision += 1;
13833
14344
  abortController.abort();
13834
14345
  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.1",
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.1",
32
32
  "echarts": "^5.6.0",
33
33
  "marked": "^18.0.5",
34
34
  "tslib": "^2.8.1",