@antglobal/copilot-cards-web 1.0.7 → 1.0.8

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
10
+ npm install @antglobal/copilot-cards-web@1.0.7
11
11
  ```
12
12
 
13
13
  `@antglobal/copilot-cards-core` is installed automatically.
@@ -50,6 +50,50 @@ card.updateVariables({ userName: "Alice" });
50
50
  card.dispose();
51
51
  ```
52
52
 
53
+ ## Stable host updates
54
+
55
+ Create the card once and keep its `CardInstance`. Input events should update
56
+ only the variables that changed. Do not call `renderCard()` again for input updates,
57
+ because remounting the card recreates image elements and can cause visible flicker.
58
+
59
+ ```ts
60
+ import {
61
+ renderCard,
62
+ type CardInstance,
63
+ type CardSchema,
64
+ } from "@antglobal/copilot-cards-web";
65
+
66
+ let card: CardInstance | null = null;
67
+
68
+ export function mountDataRechargeCard(
69
+ container: HTMLElement,
70
+ schema: CardSchema,
71
+ ): void {
72
+ card?.dispose();
73
+ card = renderCard(container, schema, {
74
+ emit: (eventName, payload) => {
75
+ if (eventName !== "dataRecharge.phone.input") return;
76
+
77
+ const value = String(
78
+ (payload as { value?: unknown } | undefined)?.value ?? "",
79
+ );
80
+ if (!card) return;
81
+ card.updateVariables({ phoneNumber: value });
82
+ },
83
+ });
84
+ }
85
+
86
+ export function unmountDataRechargeCard(): void {
87
+ card?.dispose();
88
+ card = null;
89
+ }
90
+ ```
91
+
92
+ Keep `skuOptions` and `isSkuLoading` out of each keystroke patch unless their
93
+ values actually changed. Fetching or filtering SKU data can run from the
94
+ `dataRecharge.phone.commit` event instead of remounting the card on every
95
+ `dataRecharge.phone.input` event.
96
+
53
97
  ## Framework usage
54
98
 
55
99
  The renderer accesses browser APIs and should be loaded on the client in frameworks that perform server-side rendering.
package/dist/index.d.ts CHANGED
@@ -707,6 +707,10 @@ declare class CardInput extends BaseElement {
707
707
  declare class CardImage extends BaseElement {
708
708
  static readonly is = "ai-card-image";
709
709
  private _lightbox;
710
+ private readonly _wiredImages;
711
+ private readonly _wiredLightboxes;
712
+ private readonly _wiredCloseButtons;
713
+ private readonly _handleEscape;
710
714
  protected render(): void;
711
715
  private applyHostDimension;
712
716
  private resolveDeclaredDimension;
package/dist/index.js CHANGED
@@ -2464,7 +2464,9 @@ class BaseElement extends HTMLElement {
2464
2464
  }
2465
2465
  }
2466
2466
  for (const attribute of Array.from(desired.attributes)) {
2467
- current.setAttribute(attribute.name, attribute.value);
2467
+ if (current.getAttribute(attribute.name) !== attribute.value) {
2468
+ current.setAttribute(attribute.name, attribute.value);
2469
+ }
2468
2470
  }
2469
2471
  if (current === preserved)
2470
2472
  return;
@@ -3895,6 +3897,14 @@ class CardImage extends BaseElement {
3895
3897
  constructor() {
3896
3898
  super(...arguments);
3897
3899
  this._lightbox = null;
3900
+ this._wiredImages = new WeakSet();
3901
+ this._wiredLightboxes = new WeakSet();
3902
+ this._wiredCloseButtons = new WeakSet();
3903
+ this._handleEscape = (event) => {
3904
+ if (event.key === 'Escape' && this._lightbox) {
3905
+ this.closeLightbox(this._lightbox);
3906
+ }
3907
+ };
3898
3908
  }
3899
3909
  render() {
3900
3910
  if (!this.shadowRoot || !this._node)
@@ -3935,7 +3945,7 @@ class CardImage extends BaseElement {
3935
3945
  imgStyles.push(`object-fit:${objectFit}`);
3936
3946
  if (inlineStyle)
3937
3947
  imgStyles.push(inlineStyle);
3938
- this.setShadowHTML(`
3948
+ const markup = `
3939
3949
  <style>
3940
3950
  :host {
3941
3951
  display: inline-block;
@@ -4043,11 +4053,25 @@ class CardImage extends BaseElement {
4043
4053
  loading="lazy"
4044
4054
  />
4045
4055
  </div>
4046
- <div class="lightbox">
4047
- <button class="lightbox-close" aria-label="Close">&times;</button>
4048
- <img class="lightbox-image" src="${imgSrc}" alt="${imgAlt}" />
4049
- </div>
4050
- `);
4056
+ ${preview ? `
4057
+ <div class="lightbox">
4058
+ <button class="lightbox-close" aria-label="Close">&times;</button>
4059
+ <img class="lightbox-image" src="${imgSrc}" alt="${imgAlt}" />
4060
+ </div>
4061
+ ` : ''}
4062
+ `;
4063
+ const currentImage = this.shadowRoot.querySelector('.card-image');
4064
+ const keepError = currentImage?.classList.contains('error') === true
4065
+ && currentImage.getAttribute('src') === imgSrc;
4066
+ if (this._lightbox?.classList.contains('open')) {
4067
+ this.closeLightbox(this._lightbox);
4068
+ }
4069
+ if (!currentImage
4070
+ || !this.patchShadowHTMLPreservingElement(markup, currentImage, '.card-image')) {
4071
+ this.setShadowHTML(markup);
4072
+ }
4073
+ if (keepError)
4074
+ currentImage.classList.add('error');
4051
4075
  this.bindEvents(preview);
4052
4076
  }
4053
4077
  applyHostDimension(property, styleValue, propValue) {
@@ -4076,53 +4100,66 @@ class CardImage extends BaseElement {
4076
4100
  const img = this.shadowRoot.querySelector('.card-image');
4077
4101
  const lightbox = this.shadowRoot.querySelector('.lightbox');
4078
4102
  const closeBtn = this.shadowRoot.querySelector('.lightbox-close');
4079
- if (!img || !lightbox)
4103
+ if (!img)
4080
4104
  return;
4081
4105
  // Handle image load/error states
4082
- img.addEventListener('load', () => {
4083
- img.classList.remove('loading');
4084
- });
4085
- img.addEventListener('error', () => {
4086
- img.classList.remove('loading');
4087
- img.classList.add('error');
4088
- });
4089
- if (!preview)
4106
+ if (!this._wiredImages.has(img)) {
4107
+ this._wiredImages.add(img);
4108
+ img.addEventListener('load', () => {
4109
+ img.classList.remove('loading');
4110
+ img.classList.remove('error');
4111
+ });
4112
+ img.addEventListener('error', () => {
4113
+ img.classList.remove('loading');
4114
+ img.classList.add('error');
4115
+ });
4116
+ img.addEventListener('click', (event) => {
4117
+ if (this._props.preview === false)
4118
+ return;
4119
+ const activeLightbox = this.shadowRoot?.querySelector('.lightbox');
4120
+ if (!activeLightbox)
4121
+ return;
4122
+ event.stopPropagation();
4123
+ this.openLightbox(activeLightbox);
4124
+ });
4125
+ }
4126
+ if (!preview || !lightbox || !closeBtn) {
4127
+ this._lightbox = null;
4090
4128
  return;
4091
- // Open lightbox on image click
4092
- img.addEventListener('click', (e) => {
4093
- e.stopPropagation();
4094
- this.openLightbox(lightbox);
4095
- });
4129
+ }
4096
4130
  // Close lightbox on overlay/close button click
4097
- lightbox.addEventListener('click', () => {
4098
- this.closeLightbox(lightbox);
4099
- });
4100
- closeBtn.addEventListener('click', (e) => {
4101
- e.stopPropagation();
4102
- this.closeLightbox(lightbox);
4103
- });
4131
+ if (!this._wiredLightboxes.has(lightbox)) {
4132
+ this._wiredLightboxes.add(lightbox);
4133
+ lightbox.addEventListener('click', () => {
4134
+ this.closeLightbox(lightbox);
4135
+ });
4136
+ }
4137
+ if (!this._wiredCloseButtons.has(closeBtn)) {
4138
+ this._wiredCloseButtons.add(closeBtn);
4139
+ closeBtn.addEventListener('click', (event) => {
4140
+ event.stopPropagation();
4141
+ this.closeLightbox(lightbox);
4142
+ });
4143
+ }
4104
4144
  // Close on Escape key
4105
4145
  this._lightbox = lightbox;
4106
4146
  }
4107
4147
  openLightbox(lightbox) {
4148
+ this._lightbox = lightbox;
4108
4149
  lightbox.classList.add('open');
4109
4150
  document.body.style.overflow = 'hidden';
4110
- // Add escape key listener
4111
- const handleEscape = (e) => {
4112
- if (e.key === 'Escape') {
4113
- this.closeLightbox(lightbox);
4114
- document.removeEventListener('keydown', handleEscape);
4115
- }
4116
- };
4117
- document.addEventListener('keydown', handleEscape);
4151
+ document.addEventListener('keydown', this._handleEscape);
4118
4152
  }
4119
4153
  closeLightbox(lightbox) {
4120
4154
  lightbox.classList.remove('open');
4121
4155
  document.body.style.overflow = '';
4156
+ document.removeEventListener('keydown', this._handleEscape);
4122
4157
  }
4123
4158
  disconnectedCallback() {
4124
4159
  // Ensure body overflow is restored when element is removed
4125
4160
  document.body.style.overflow = '';
4161
+ document.removeEventListener('keydown', this._handleEscape);
4162
+ this._lightbox = null;
4126
4163
  }
4127
4164
  }
4128
4165
  CardImage.is = 'ai-card-image';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antglobal/copilot-cards-web",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Web Component renderer for copilot bot card SDK — PC + Mobile + WebView unified rendering via Custom Elements",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "license": "MIT",
30
30
  "dependencies": {
31
- "@antglobal/copilot-cards-core": "^1.0.5",
31
+ "@antglobal/copilot-cards-core": "^1.0.8",
32
32
  "echarts": "^5.6.0",
33
33
  "marked": "^18.0.5",
34
34
  "tslib": "^2.8.1",