@descope-ui/common 0.0.2 → 0.0.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [0.0.4](https://github.com/descope/web-components-ui/compare/@descope-ui/common-0.0.3...@descope-ui/common-0.0.4) (2025-02-17)
6
+
7
+ ## [0.0.3](https://github.com/descope/web-components-ui/compare/@descope-ui/common-0.0.2...@descope-ui/common-0.0.3) (2025-02-04)
8
+
5
9
  ## [0.0.2](https://github.com/descope/web-components-ui/compare/@descope-ui/common-0.0.1...@descope-ui/common-0.0.2) (2025-01-28)
6
10
 
7
11
  ## 0.0.1 (2025-01-28)
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@descope-ui/common",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "dependencies": {
5
5
  "element-internals-polyfill": "^1.3.9",
6
6
  "color": "^4.2.3",
7
+ "lodash.debounce": "4.0.8",
7
8
  "lodash.merge": "4.6.2"
8
9
  },
9
10
  "exports": {
@@ -0,0 +1,70 @@
1
+ export const CONNECTOR_ERRORS = {
2
+ CONNECTOR_INVALID: 'CONNECTOR_INVALID',
3
+ FETCH_RESULTS_ERROR: 'FETCH_RESULTS_ERROR',
4
+ };
5
+
6
+ export const createBaseConnectorClass = () =>
7
+ class BaseConnectorClass {
8
+ constructor(getAttribute) {
9
+ this.getAttribute = getAttribute;
10
+ this.isValid = this.#validateConfig();
11
+ }
12
+
13
+ checkConnectorValidity() {
14
+ this.isValid = this.#validateConfig();
15
+ }
16
+
17
+ #validateConfig() {
18
+ const missingOrInvalidParams = this.getRequiredParams().filter(
19
+ (param) => {
20
+ const value = this.getAttribute(param);
21
+ return !value || !this.validateParam(param, value);
22
+ },
23
+ );
24
+
25
+ if (missingOrInvalidParams.length) {
26
+ // eslint-disable-next-line no-console
27
+ console.error(
28
+ `[${this.constructor.name}] Invalid configuration. Issues with parameters:`,
29
+ missingOrInvalidParams.map((param) => ({
30
+ param,
31
+ value: this.getAttribute(param),
32
+ })),
33
+ );
34
+ return false;
35
+ }
36
+ return true;
37
+ }
38
+
39
+ // eslint-disable-next-line class-methods-use-this
40
+ validateParam(param, value) {
41
+ // Base validation - can be overridden by specific connectors
42
+ return value.trim().length > 0;
43
+ }
44
+
45
+ // eslint-disable-next-line class-methods-use-this
46
+ getRequiredParams() {
47
+ // eslint-disable-next-line no-console
48
+ console.error('Connector must implement getRequiredParams');
49
+ return [];
50
+ }
51
+
52
+ async fetch(query) {
53
+ if (!this.isValid) {
54
+ // eslint-disable-next-line no-console
55
+ console.error(
56
+ `[${this.constructor.name}] Cannot fetch results: connector is not properly configured`,
57
+ );
58
+ return { results: [], error: CONNECTOR_ERRORS.CONNECTOR_INVALID };
59
+ }
60
+
61
+ return this._fetchResults(query);
62
+ }
63
+
64
+ // eslint-disable-next-line class-methods-use-this, no-unused-vars
65
+ _fetchResults(query) {
66
+ // eslint-disable-next-line no-console
67
+ console.error('Connector must implement fetchResults');
68
+ return { results: [], error: undefined };
69
+ }
70
+ };
@@ -1,3 +1,4 @@
1
1
  export * from './baseClasses/createBaseClass';
2
2
  export * from './baseClasses/createCssVarImageClass';
3
3
  export * from './baseClasses/createBaseInputClass';
4
+ export * from './baseClasses/createBaseConnectorClass';
@@ -0,0 +1,68 @@
1
+ export const connectorMixin =
2
+ ({ connectorClasses }) =>
3
+ (superclass) =>
4
+ class ConnectorMixinClass extends superclass {
5
+ #connectorClasses = connectorClasses;
6
+
7
+ static get observedAttributes() {
8
+ return [...(superclass.observedAttributes || []), 'connector-template'];
9
+ }
10
+
11
+ get connectorClasses() {
12
+ return this.#connectorClasses;
13
+ }
14
+
15
+ set connectorClasses(value) {
16
+ this.#connectorClasses = value;
17
+ }
18
+
19
+ get connectorTemplate() {
20
+ return this.getAttribute('connector-template');
21
+ }
22
+
23
+ initializeConnector() {
24
+ const template = this.connectorTemplate;
25
+
26
+ if (!this.connectorClasses[template]) {
27
+ // eslint-disable-next-line no-console
28
+ console.error(`Unsupported connector template: ${template}`);
29
+ return;
30
+ }
31
+ const ConnectorClass = this.connectorClasses[template];
32
+ this.connector = new ConnectorClass(this.getAttribute.bind(this));
33
+ }
34
+
35
+ async fetchConnectorResults(query) {
36
+ if (!this.connector) {
37
+ // eslint-disable-next-line no-console
38
+ console.error('No connector initialized for the field');
39
+ return { results: [] };
40
+ }
41
+
42
+ const { results, error } = await this.connector.fetch(query);
43
+ if (error) {
44
+ return { results: [], error };
45
+ }
46
+ return {
47
+ results: results.map(({ label, value }) => ({
48
+ label,
49
+ value,
50
+ })),
51
+ };
52
+ }
53
+
54
+ init() {
55
+ super.init?.();
56
+ this.initializeConnector();
57
+ }
58
+
59
+ attributeChangedCallback(attrName, oldValue, newValue) {
60
+ super.attributeChangedCallback?.(attrName, oldValue, newValue);
61
+
62
+ if (oldValue !== newValue) {
63
+ if (attrName === 'connector-template') {
64
+ this.initializeConnector();
65
+ }
66
+ }
67
+ }
68
+ };
@@ -11,4 +11,5 @@ export { normalizeBooleanAttributesMixin } from './normalizeBooleanAttributesMix
11
11
  export { lifecycleEventsMixin } from './lifecycleEventsMixin';
12
12
  export { inputEventsDispatchingMixin } from './inputEventsDispatchingMixin';
13
13
  export { externalInputMixin } from './externalInputMixin';
14
- export {componentsContextMixin} from './componentsContextMixin'
14
+ export { componentsContextMixin } from './componentsContextMixin';
15
+ export { connectorMixin } from './connectorMixin';
@@ -0,0 +1,58 @@
1
+ import debounce from 'lodash.debounce';
2
+
3
+ /**
4
+ * Creates a debounced version of an async function that manages multiple pending promises and ensures proper error handling.
5
+ * When multiple calls are made within the wait period, all pending promises will be resolved/rejected with the result/error
6
+ * of the most recent call.
7
+ *
8
+ * @param {Function} func - The async function to debounce.
9
+ * @param {number} wait - The number of milliseconds to delay before calling the function.
10
+ * @returns {Function} A debounced version of the input function that returns a promise which will resolve/reject based on
11
+ * the most recent call's result.
12
+ *
13
+ * @example
14
+ * const debouncedSearch = asyncDebounce(async (query) => {
15
+ * const results = await api.search(query);
16
+ * return results;
17
+ * }, 300);
18
+ *
19
+ * // Multiple calls within 300ms will all resolve with the results from the last call
20
+ * const promise1 = debouncedSearch('search');
21
+ * const promise2 = debouncedSearch('search term');
22
+ * // Both promises will resolve with results from 'search term'
23
+ *
24
+ * // If the last call throws an error, all pending promises will be rejected
25
+ * try {
26
+ * await debouncedSearch('error term');
27
+ * } catch (error) {
28
+ * // Handle error
29
+ * }
30
+ */
31
+ export const asyncDebounce = (func, wait) => {
32
+ let currentId = 0;
33
+ const resolveSet = new Set();
34
+ const rejectSet = new Set();
35
+
36
+ const debounced = debounce((args, requestId) => {
37
+ func(...args)
38
+ .then((result) => {
39
+ if (requestId === currentId) {
40
+ resolveSet.forEach((resolve) => resolve(result));
41
+ resolveSet.clear();
42
+ }
43
+ })
44
+ .catch((error) => {
45
+ rejectSet.forEach((reject) => reject(error));
46
+ rejectSet.clear();
47
+ });
48
+ }, wait);
49
+
50
+ return (...args) => {
51
+ currentId++; // Increment for new request
52
+ return new Promise((resolve, reject) => {
53
+ resolveSet.add(resolve);
54
+ rejectSet.add(reject);
55
+ debounced(args, currentId);
56
+ });
57
+ };
58
+ };
@@ -1,10 +1,13 @@
1
+ export { asyncDebounce } from './asyncDebounce';
2
+
1
3
  export const kebabCase = (str) =>
2
4
  str
3
5
  .replace(/([a-z])([A-Z])/g, '$1-$2')
4
6
  .replace(/[\s_.]+/g, '-')
5
7
  .toLowerCase();
6
8
 
7
- export const kebabCaseJoin = (...args) => kebabCase(args.filter((arg) => !!arg).join('-'));
9
+ export const kebabCaseJoin = (...args) =>
10
+ kebabCase(args.filter((arg) => !!arg).join('-'));
8
11
 
9
12
  export const upperFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
10
13