@adobe-commerce/elsie 1.5.0-beta1 → 1.5.0-beta2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe-commerce/elsie",
3
- "version": "1.5.0-beta1",
3
+ "version": "1.5.0-beta2",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "description": "Domain Package SDK",
6
6
  "engines": {
@@ -8,12 +8,13 @@
8
8
  *******************************************************************/
9
9
 
10
10
  import { Icon } from '@adobe-commerce/elsie/components';
11
- import '@adobe-commerce/elsie/components/Picker/Picker.css';
12
11
  import { ChevronDown } from '@adobe-commerce/elsie/icons';
13
12
  import { classes } from '@adobe-commerce/elsie/lib';
14
13
  import { FunctionComponent, VNode } from 'preact';
15
14
  import { HTMLAttributes, useEffect, useState } from 'preact/compat';
16
15
 
16
+ import '@adobe-commerce/elsie/components/Picker/Picker.css';
17
+
17
18
  type PickerValue = string | null;
18
19
 
19
20
  export interface PickerOption {
@@ -68,9 +69,10 @@ export const Picker: FunctionComponent<PickerProps> = ({
68
69
  defaultOption,
69
70
  icon,
70
71
  className,
72
+ id,
71
73
  ...props
72
74
  }) => {
73
- const id = props?.id || name || `dropin-picker-${Math.random().toString(36)}`;
75
+ const uniqueId = id ?? name ?? `dropin-picker-${Math.random().toString(36)}`;
74
76
  const isRequired = !!props?.required;
75
77
 
76
78
  // find the first option that is not disabled
@@ -154,7 +156,7 @@ export const Picker: FunctionComponent<PickerProps> = ({
154
156
  )}
155
157
 
156
158
  <select
157
- id={id}
159
+ id={uniqueId}
158
160
  className={classes([
159
161
  'dropin-picker__select',
160
162
  `dropin-picker__select--${variant}`,
@@ -14,6 +14,7 @@ import {
14
14
  useEffect,
15
15
  useRef,
16
16
  useCallback,
17
+ useMemo,
17
18
  } from 'preact/compat';
18
19
  import { classes } from '@adobe-commerce/elsie/lib';
19
20
  import '@adobe-commerce/elsie/components/TextSwatch/TextSwatch.css';
@@ -93,6 +94,8 @@ export const TextSwatch: FunctionComponent<TextSwatchProps> = ({
93
94
  }
94
95
  }, [label]);
95
96
 
97
+ const uniqueId = useMemo(() => id ?? `${name}_${id}_${Math.random().toString(36)}`, [name, id]);
98
+
96
99
  return (
97
100
  <div
98
101
  className="dropin-text-swatch__container"
@@ -101,7 +104,7 @@ export const TextSwatch: FunctionComponent<TextSwatchProps> = ({
101
104
  <input
102
105
  type={multi ? 'checkbox' : 'radio'}
103
106
  name={name}
104
- id={id}
107
+ id={uniqueId}
105
108
  value={value}
106
109
  aria-label={handleAriaLabel()}
107
110
  checked={selected}
@@ -116,7 +119,7 @@ export const TextSwatch: FunctionComponent<TextSwatchProps> = ({
116
119
  ])}
117
120
  />
118
121
  <label
119
- htmlFor={id}
122
+ htmlFor={uniqueId}
120
123
  ref={spanRef}
121
124
  className={classes([
122
125
  'dropin-text-swatch__label',
@@ -121,5 +121,21 @@ button.addEventListener('click', () => {
121
121
  });
122
122
  ```
123
123
 
124
+ ### Unmounting components without instance access
125
+
126
+ The `Render.unmount` static method provides a way to unmount components from the DOM when you don't have direct access to the component instance.
127
+ This is particularly useful in scenarios where components are rendered inside modals, dialogs, or other temporary containers that need to be cleaned up.
128
+
129
+ #### Example
130
+
131
+ ```js
132
+ // Close the dialog
133
+ dialog.close();
134
+
135
+ // Unmount any dropin containers rendered in the modal
136
+ dialog.querySelectorAll('[data-dropin-container]').forEach(Render.unmount);
137
+ ```
138
+
139
+ This approach ensures that all dropin components are properly cleaned up when their container elements are removed from the DOM, preventing memory leaks and maintaining application performance.
124
140
  </Unstyled>
125
141
 
@@ -71,17 +71,21 @@ export class Render {
71
71
  rootElement.innerHTML = '';
72
72
 
73
73
  // clone the root element to initialize rendering on the background
74
- const tmp = document.createElement('div');
74
+ const root = document.createElement('div');
75
75
 
76
76
  // apply base design tokens and global styles to the root element
77
77
  rootElement.classList.add('dropin-design');
78
+ rootElement.setAttribute('data-dropin-container', Component.name);
78
79
 
79
- render(<Root next={state} />, tmp);
80
+ // store the virtual root element
81
+ (rootElement as any).__rootElement = root;
82
+
83
+ render(<Root next={state} />, root);
80
84
 
81
85
  // API object to control the rendered component
82
86
  const API: RenderAPI = {
83
87
  remove: () => {
84
- render(null, tmp);
88
+ render(null, root);
85
89
  },
86
90
  setProps: (cb: (prev: T) => T) => {
87
91
  const next = cb(state.peek());
@@ -97,7 +101,7 @@ export class Render {
97
101
  rootElement.classList.add('dropin-design');
98
102
 
99
103
  // append the rendered component to the DOM only when all slots are resolved
100
- rootElement.appendChild(tmp.firstChild ?? tmp);
104
+ rootElement.appendChild(root.firstChild ?? root);
101
105
 
102
106
  return resolve(API);
103
107
  }
@@ -106,14 +110,24 @@ export class Render {
106
110
  };
107
111
  }
108
112
 
113
+ /**
114
+ * Unmounts a container from a root element.
115
+ * @param rootElement - The root element to unmount the container from.
116
+ */
117
+ static unmount(rootElement: HTMLElement) {
118
+ const root = (rootElement as any)?.__rootElement;
119
+ if (!root) throw new Error('Root element is not defined');
120
+ render(null, root);
121
+ }
122
+
109
123
  /**
110
124
  * UnRenders a component from a root element.
111
125
  * @param rootElement - The root element to unmount the component from.
112
- * @deprecated Use `remove` method from the returned object of the `mount` method instead.
126
+ * @deprecated Use `remove` method from the returned object of the `mount` method instead or `unmount` method from the `Render` class.
113
127
  */
114
128
  unmount(rootElement: HTMLElement) {
115
129
  if (!rootElement) throw new Error('Root element is not defined');
116
- rootElement.firstChild?.remove();
130
+ rootElement.firstChild?.remove();
117
131
  }
118
132
 
119
133
  /**
@@ -135,4 +149,4 @@ export class Render {
135
149
  { ...options }
136
150
  );
137
151
  }
138
- }
152
+ }