@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 +4 -0
- package/package.json +2 -1
- package/src/baseClasses/baseClasses/createBaseConnectorClass.js +70 -0
- package/src/baseClasses/index.js +1 -0
- package/src/componentsMixins/mixins/connectorMixin.js +68 -0
- package/src/componentsMixins/mixins/index.js +2 -1
- package/src/utils/asyncDebounce.js +58 -0
- package/src/utils/index.js +4 -1
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
|
@@ -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
|
+
};
|
package/src/baseClasses/index.js
CHANGED
|
@@ -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
|
+
};
|
package/src/utils/index.js
CHANGED
|
@@ -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) =>
|
|
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
|
|