assign-gingerly 0.0.22 → 0.0.24
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 +713 -40
- package/assignGingerly.js +193 -9
- package/assignGingerly.ts +237 -9
- package/handleIshProperty.js +92 -0
- package/handleIshProperty.ts +115 -0
- package/index.js +1 -1
- package/index.ts +1 -1
- package/object-extension.js +20 -1
- package/object-extension.ts +23 -2
- package/package.json +3 -3
- package/parseWithAttrs.js +29 -23
- package/parseWithAttrs.ts +41 -24
- package/playwright.config.ts +9 -9
- package/types/assign-gingerly/types.d.ts +80 -23
- package/types/global.d.ts +4 -0
- package/waitForEvent.js +15 -1
- package/waitForEvent.ts +19 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { IAssignGingerlyOptions, ItemscopeManagerConfig } from './assignGingerly.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handle the 'ish' property assignment for HTMLElements with itemscope attributes.
|
|
5
|
+
* This function validates the element and value, then defines or updates the 'ish' property.
|
|
6
|
+
*
|
|
7
|
+
* @param element - The HTMLElement to assign the 'ish' property to
|
|
8
|
+
* @param value - The value to assign (must be an object)
|
|
9
|
+
* @param options - Optional assignGingerly options
|
|
10
|
+
* @param assignGingerlyFn - Reference to the assignGingerly function for recursive calls
|
|
11
|
+
*/
|
|
12
|
+
export async function handleIshProperty(
|
|
13
|
+
element: HTMLElement,
|
|
14
|
+
value: any,
|
|
15
|
+
options: IAssignGingerlyOptions | undefined,
|
|
16
|
+
assignGingerlyFn: (target: any, source: any, options?: IAssignGingerlyOptions) => any
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
// Validate itemscope attribute
|
|
19
|
+
const itemscopeValue = element.getAttribute('itemscope');
|
|
20
|
+
if (typeof itemscopeValue !== 'string' || itemscopeValue.length === 0) {
|
|
21
|
+
throw new Error('Element must have itemscope attribute set to a non-empty string value');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate value is an object
|
|
25
|
+
if (typeof value !== 'object' || value === null) {
|
|
26
|
+
throw new Error('ish property value must be an object');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get or create the 'ish' property on the element
|
|
30
|
+
if (!('ish' in element)) {
|
|
31
|
+
await defineIshProperty(element, itemscopeValue, options, assignGingerlyFn);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Queue the value for assignment
|
|
35
|
+
const ishDescriptor = Object.getOwnPropertyDescriptor(element, 'ish');
|
|
36
|
+
if (ishDescriptor && ishDescriptor.set) {
|
|
37
|
+
ishDescriptor.set.call(element, value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Define the 'ish' property on an HTMLElement with itemscope attribute.
|
|
43
|
+
* This function handles both immediate and lazy manager instantiation.
|
|
44
|
+
*
|
|
45
|
+
* @param element - The HTMLElement to define the 'ish' property on
|
|
46
|
+
* @param managerName - The name of the manager (from itemscope attribute)
|
|
47
|
+
* @param options - Optional assignGingerly options
|
|
48
|
+
* @param assignGingerlyFn - Reference to the assignGingerly function for recursive calls
|
|
49
|
+
*/
|
|
50
|
+
async function defineIshProperty(
|
|
51
|
+
element: HTMLElement,
|
|
52
|
+
managerName: string,
|
|
53
|
+
options: IAssignGingerlyOptions | undefined,
|
|
54
|
+
assignGingerlyFn: (target: any, source: any, options?: IAssignGingerlyOptions) => any
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
// Determine which registry to use
|
|
57
|
+
const registry = (element as any).customElementRegistry?.itemscopeRegistry
|
|
58
|
+
?? (typeof customElements !== 'undefined' ? customElements.itemscopeRegistry : undefined);
|
|
59
|
+
|
|
60
|
+
if (!registry) {
|
|
61
|
+
throw new Error('ItemscopeRegistry not available');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check if manager is registered
|
|
65
|
+
let config = registry.get(managerName);
|
|
66
|
+
|
|
67
|
+
// If not registered, wait for registration
|
|
68
|
+
if (!config) {
|
|
69
|
+
const { waitForEvent } = await import('./waitForEvent.js');
|
|
70
|
+
await waitForEvent(registry, managerName);
|
|
71
|
+
config = registry.get(managerName);
|
|
72
|
+
|
|
73
|
+
if (!config) {
|
|
74
|
+
throw new Error(`Manager "${managerName}" not found after registration event`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create manager instance
|
|
79
|
+
let managerInstance: any = null;
|
|
80
|
+
const valueQueue: any[] = [];
|
|
81
|
+
|
|
82
|
+
// Define the 'ish' property
|
|
83
|
+
Object.defineProperty(element, 'ish', {
|
|
84
|
+
get() {
|
|
85
|
+
return managerInstance;
|
|
86
|
+
},
|
|
87
|
+
set(newValue: any) {
|
|
88
|
+
// If setting the same instance, do nothing
|
|
89
|
+
if (newValue === managerInstance) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Queue the value
|
|
94
|
+
valueQueue.push(newValue);
|
|
95
|
+
|
|
96
|
+
// If manager not yet instantiated, create it
|
|
97
|
+
if (!managerInstance) {
|
|
98
|
+
// Merge all queued values for initVals
|
|
99
|
+
const initVals = Object.assign({}, ...valueQueue);
|
|
100
|
+
managerInstance = new config!.manager(element, initVals);
|
|
101
|
+
valueQueue.length = 0; // Clear queue
|
|
102
|
+
} else {
|
|
103
|
+
// Process queue asynchronously
|
|
104
|
+
(async () => {
|
|
105
|
+
while (valueQueue.length > 0) {
|
|
106
|
+
const queuedValue = valueQueue.shift();
|
|
107
|
+
await assignGingerlyFn(managerInstance, queuedValue, options);
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
enumerable: true,
|
|
113
|
+
configurable: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { assignGingerly } from './assignGingerly.js';
|
|
2
2
|
export { assignTentatively } from './assignTentatively.js';
|
|
3
|
-
export { EnhancementRegistry } from './assignGingerly.js';
|
|
3
|
+
export { EnhancementRegistry, ItemscopeRegistry, EnhancementRegisteredEvent } from './assignGingerly.js';
|
|
4
4
|
export { waitForEvent } from './waitForEvent.js';
|
|
5
5
|
export { ParserRegistry, globalParserRegistry } from './parserRegistry.js';
|
|
6
6
|
export { parseWithAttrs } from './parseWithAttrs.js';
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export {assignGingerly} from './assignGingerly.js';
|
|
2
2
|
export {assignTentatively} from './assignTentatively.js';
|
|
3
|
-
export {EnhancementRegistry} from './assignGingerly.js';
|
|
3
|
+
export {EnhancementRegistry, ItemscopeRegistry, EnhancementRegisteredEvent} from './assignGingerly.js';
|
|
4
4
|
export {waitForEvent} from './waitForEvent.js';
|
|
5
5
|
export {ParserRegistry, globalParserRegistry} from './parserRegistry.js';
|
|
6
6
|
export {parseWithAttrs} from './parseWithAttrs.js';
|
package/object-extension.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import assignGingerly, { EnhancementRegistry, getInstanceMap } from './assignGingerly.js';
|
|
1
|
+
import assignGingerly, { EnhancementRegistry, ItemscopeRegistry, getInstanceMap } from './assignGingerly.js';
|
|
2
2
|
import { parseWithAttrs } from './parseWithAttrs.js';
|
|
3
3
|
/**
|
|
4
4
|
* Normalizes lifecycleKeys to always return an object with dispose and resolved keys
|
|
@@ -34,6 +34,25 @@ if (typeof CustomElementRegistry !== 'undefined') {
|
|
|
34
34
|
enumerable: false,
|
|
35
35
|
configurable: true,
|
|
36
36
|
});
|
|
37
|
+
/**
|
|
38
|
+
* Adds itemscopeRegistry to CustomElementRegistry prototype as a lazy getter
|
|
39
|
+
*/
|
|
40
|
+
Object.defineProperty(CustomElementRegistry.prototype, 'itemscopeRegistry', {
|
|
41
|
+
get: function () {
|
|
42
|
+
// Create a new ItemscopeRegistry instance on first access and cache it
|
|
43
|
+
const registry = new ItemscopeRegistry();
|
|
44
|
+
// Replace the getter with the actual value
|
|
45
|
+
Object.defineProperty(this, 'itemscopeRegistry', {
|
|
46
|
+
value: registry,
|
|
47
|
+
writable: true,
|
|
48
|
+
enumerable: false,
|
|
49
|
+
configurable: true,
|
|
50
|
+
});
|
|
51
|
+
return registry;
|
|
52
|
+
},
|
|
53
|
+
enumerable: false,
|
|
54
|
+
configurable: true,
|
|
55
|
+
});
|
|
37
56
|
}
|
|
38
57
|
/**
|
|
39
58
|
* Enhancement container class for Element.prototype.enh
|
package/object-extension.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import assignGingerly, { EnhancementRegistry, IAssignGingerlyOptions, getInstanceMap, INSTANCE_MAP_GUID } from './assignGingerly.js';
|
|
1
|
+
import assignGingerly, { EnhancementRegistry, ItemscopeRegistry, IAssignGingerlyOptions, getInstanceMap, INSTANCE_MAP_GUID } from './assignGingerly.js';
|
|
2
2
|
import { parseWithAttrs } from './parseWithAttrs.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,11 +17,12 @@ function normalizeLifecycleKeys(lifecycleKeys: true | { dispose?: string | symbo
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Extends the CustomElementRegistry interface to include enhancementRegistry
|
|
20
|
+
* Extends the CustomElementRegistry interface to include enhancementRegistry and itemscopeRegistry
|
|
21
21
|
*/
|
|
22
22
|
declare global {
|
|
23
23
|
interface CustomElementRegistry {
|
|
24
24
|
enhancementRegistry: typeof EnhancementRegistry | EnhancementRegistry;
|
|
25
|
+
itemscopeRegistry: ItemscopeRegistry;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
interface Element {
|
|
@@ -97,6 +98,26 @@ if (typeof CustomElementRegistry !== 'undefined') {
|
|
|
97
98
|
enumerable: false,
|
|
98
99
|
configurable: true,
|
|
99
100
|
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Adds itemscopeRegistry to CustomElementRegistry prototype as a lazy getter
|
|
104
|
+
*/
|
|
105
|
+
Object.defineProperty(CustomElementRegistry.prototype, 'itemscopeRegistry', {
|
|
106
|
+
get: function () {
|
|
107
|
+
// Create a new ItemscopeRegistry instance on first access and cache it
|
|
108
|
+
const registry = new ItemscopeRegistry();
|
|
109
|
+
// Replace the getter with the actual value
|
|
110
|
+
Object.defineProperty(this, 'itemscopeRegistry', {
|
|
111
|
+
value: registry,
|
|
112
|
+
writable: true,
|
|
113
|
+
enumerable: false,
|
|
114
|
+
configurable: true,
|
|
115
|
+
});
|
|
116
|
+
return registry;
|
|
117
|
+
},
|
|
118
|
+
enumerable: false,
|
|
119
|
+
configurable: true,
|
|
120
|
+
});
|
|
100
121
|
}
|
|
101
122
|
|
|
102
123
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "This package provides a utility function for carefully merging one object into another.",
|
|
5
5
|
"homepage": "https://github.com/bahrus/assign-gingerly#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@playwright/test": "1.59.0-alpha-2026-02-28",
|
|
67
67
|
"spa-ssi": "0.0.27",
|
|
68
|
-
"@types/node": "
|
|
69
|
-
"typescript": "
|
|
68
|
+
"@types/node": "25.5.0",
|
|
69
|
+
"typescript": "6.0.2"
|
|
70
70
|
}
|
|
71
71
|
}
|
package/parseWithAttrs.js
CHANGED
|
@@ -23,35 +23,38 @@ function resolveParser(parserSpec) {
|
|
|
23
23
|
if (typeof parserSpec === 'function') {
|
|
24
24
|
return parserSpec;
|
|
25
25
|
}
|
|
26
|
-
//
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
const ctr = customElements.get(elementName);
|
|
37
|
-
if (ctr && typeof ctr[methodName] === 'function') {
|
|
38
|
-
return ctr[methodName];
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch (e) {
|
|
42
|
-
// customElements.get might throw, fall through to registry
|
|
43
|
-
}
|
|
26
|
+
// Tuple [CustomElementName, StaticMethodName] - resolve custom element static method
|
|
27
|
+
if (Array.isArray(parserSpec)) {
|
|
28
|
+
const [elementName, methodName] = parserSpec;
|
|
29
|
+
if (typeof customElements === 'undefined') {
|
|
30
|
+
throw new Error(`Cannot resolve parser [${elementName}, ${methodName}]: customElements is not available`);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const ctr = customElements.get(elementName);
|
|
34
|
+
if (!ctr) {
|
|
35
|
+
throw new Error(`Cannot resolve parser [${elementName}, ${methodName}]: custom element "${elementName}" not found`);
|
|
44
36
|
}
|
|
45
|
-
|
|
37
|
+
if (typeof ctr[methodName] !== 'function') {
|
|
38
|
+
throw new Error(`Cannot resolve parser [${elementName}, ${methodName}]: static method "${methodName}" not found on custom element "${elementName}"`);
|
|
39
|
+
}
|
|
40
|
+
return ctr[methodName];
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
if (e instanceof Error && e.message.startsWith('Cannot resolve parser')) {
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Cannot resolve parser [${elementName}, ${methodName}]: ${e instanceof Error ? e.message : String(e)}`);
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
+
}
|
|
49
|
+
// String reference - resolve from global registry
|
|
50
|
+
if (typeof parserSpec === 'string') {
|
|
48
51
|
const parser = globalParserRegistry.get(parserSpec);
|
|
49
52
|
if (parser) {
|
|
50
53
|
return parser;
|
|
51
54
|
}
|
|
52
|
-
// Not found
|
|
53
|
-
throw new Error(`Parser "${parserSpec}" not found. ` +
|
|
54
|
-
`
|
|
55
|
+
// Not found in registry
|
|
56
|
+
throw new Error(`Parser "${parserSpec}" not found in globalParserRegistry. ` +
|
|
57
|
+
`If you want to reference a custom element static method, use tuple syntax: ["element-name", "methodName"]`);
|
|
55
58
|
}
|
|
56
59
|
return undefined;
|
|
57
60
|
}
|
|
@@ -71,6 +74,9 @@ function getCacheKey(config) {
|
|
|
71
74
|
else if (typeof config.parser === 'string') {
|
|
72
75
|
parserStr = `named:${config.parser}`;
|
|
73
76
|
}
|
|
77
|
+
else if (Array.isArray(config.parser)) {
|
|
78
|
+
parserStr = `tuple:${config.parser[0]}.${config.parser[1]}`;
|
|
79
|
+
}
|
|
74
80
|
else {
|
|
75
81
|
parserStr = 'custom';
|
|
76
82
|
}
|
package/parseWithAttrs.ts
CHANGED
|
@@ -17,7 +17,9 @@ const parseCache = new Map<string, Map<string, any>>();
|
|
|
17
17
|
* @returns The resolved parser function
|
|
18
18
|
* @throws Error if parser cannot be resolved
|
|
19
19
|
*/
|
|
20
|
-
function resolveParser(
|
|
20
|
+
function resolveParser(
|
|
21
|
+
parserSpec: ((v: string | null) => any) | string | [string, string] | undefined
|
|
22
|
+
): ((v: string | null) => any) | undefined {
|
|
21
23
|
// Undefined - no parser specified
|
|
22
24
|
if (parserSpec === undefined) {
|
|
23
25
|
return undefined;
|
|
@@ -28,39 +30,52 @@ function resolveParser(parserSpec: ((v: string | null) => any) | string | undefi
|
|
|
28
30
|
return parserSpec;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
//
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
// Tuple [CustomElementName, StaticMethodName] - resolve custom element static method
|
|
34
|
+
if (Array.isArray(parserSpec)) {
|
|
35
|
+
const [elementName, methodName] = parserSpec;
|
|
36
|
+
|
|
37
|
+
if (typeof customElements === 'undefined') {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Cannot resolve parser [${elementName}, ${methodName}]: customElements is not available`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const ctr = customElements.get(elementName);
|
|
45
|
+
if (!ctr) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Cannot resolve parser [${elementName}, ${methodName}]: custom element "${elementName}" not found`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (ctr && typeof (ctr as any)[methodName] === 'function') {
|
|
44
|
-
return (ctr as any)[methodName];
|
|
45
|
-
}
|
|
46
|
-
} catch (e) {
|
|
47
|
-
// customElements.get might throw, fall through to registry
|
|
48
|
-
}
|
|
51
|
+
if (typeof (ctr as any)[methodName] !== 'function') {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Cannot resolve parser [${elementName}, ${methodName}]: static method "${methodName}" not found on custom element "${elementName}"`
|
|
54
|
+
);
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
return (ctr as any)[methodName];
|
|
58
|
+
} catch (e) {
|
|
59
|
+
if (e instanceof Error && e.message.startsWith('Cannot resolve parser')) {
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Cannot resolve parser [${elementName}, ${methodName}]: ${e instanceof Error ? e.message : String(e)}`
|
|
64
|
+
);
|
|
52
65
|
}
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// String reference - resolve from global registry
|
|
69
|
+
if (typeof parserSpec === 'string') {
|
|
55
70
|
const parser = globalParserRegistry.get(parserSpec);
|
|
56
71
|
if (parser) {
|
|
57
72
|
return parser;
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
// Not found
|
|
75
|
+
// Not found in registry
|
|
61
76
|
throw new Error(
|
|
62
|
-
`Parser "${parserSpec}" not found. ` +
|
|
63
|
-
`
|
|
77
|
+
`Parser "${parserSpec}" not found in globalParserRegistry. ` +
|
|
78
|
+
`If you want to reference a custom element static method, use tuple syntax: ["element-name", "methodName"]`
|
|
64
79
|
);
|
|
65
80
|
}
|
|
66
81
|
|
|
@@ -82,6 +97,8 @@ function getCacheKey(config: AttrConfig<any>): string {
|
|
|
82
97
|
parserStr = 'builtin';
|
|
83
98
|
} else if (typeof config.parser === 'string') {
|
|
84
99
|
parserStr = `named:${config.parser}`;
|
|
100
|
+
} else if (Array.isArray(config.parser)) {
|
|
101
|
+
parserStr = `tuple:${config.parser[0]}.${config.parser[1]}`;
|
|
85
102
|
} else {
|
|
86
103
|
parserStr = 'custom';
|
|
87
104
|
}
|
package/playwright.config.ts
CHANGED
|
@@ -20,7 +20,7 @@ export default defineConfig({
|
|
|
20
20
|
/* Opt out of parallel tests on CI. */
|
|
21
21
|
workers: process.env.CI ? 1 : undefined,
|
|
22
22
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
23
|
-
reporter: 'html',
|
|
23
|
+
reporter: [ ['html', { open: 'never' }] ],
|
|
24
24
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
25
25
|
use: {
|
|
26
26
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
@@ -34,15 +34,15 @@ export default defineConfig({
|
|
|
34
34
|
use: { ...devices['Desktop Chrome'] },
|
|
35
35
|
},
|
|
36
36
|
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
},
|
|
37
|
+
// {
|
|
38
|
+
// name: 'firefox',
|
|
39
|
+
// use: { ...devices['Desktop Firefox'] },
|
|
40
|
+
// },
|
|
41
41
|
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
},
|
|
42
|
+
// {
|
|
43
|
+
// name: 'webkit',
|
|
44
|
+
// use: { ...devices['Desktop Safari'] },
|
|
45
|
+
// },
|
|
46
46
|
],
|
|
47
47
|
|
|
48
48
|
/* Run your local dev server before starting the tests */
|
|
@@ -30,35 +30,40 @@ export type Spawner<T = any, Obj = Element> = {
|
|
|
30
30
|
canSpawn?: (obj: any, ctx?: SpawnContext<T>) => boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export interface EnhancementConfigBase<T = any> {
|
|
34
|
+
//Allow unprefixed attributes for custom elements and SVG when element tag name matches pattern
|
|
35
|
+
allowUnprefixed?: string | RegExp;
|
|
36
|
+
|
|
37
|
+
//keys of type symbol are used for dependency injection
|
|
38
|
+
//and are used by assign-gingerly
|
|
39
|
+
symlinks?: { [key: symbol]: keyof T };
|
|
40
|
+
|
|
41
|
+
lifecycleKeys?:
|
|
42
|
+
| true // Use standard names: "dispose" method, "resolved" property/event
|
|
43
|
+
| {
|
|
44
|
+
dispose?: string | symbol,
|
|
45
|
+
resolved?: string | symbol
|
|
46
|
+
}
|
|
47
|
+
//used by mount-observer, not by assign-gingerly
|
|
48
|
+
//impossible to polyfill, but will always be disposed
|
|
49
|
+
//when oElement's reference count goes to zero
|
|
50
|
+
disposeOn?: DisposeEvent | DisposeEvent[]
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
/**
|
|
34
54
|
* Configuration for enhancing elements with class instances
|
|
35
55
|
* Defines how to spawn and initialize enhancement classes
|
|
36
56
|
*/
|
|
37
|
-
export interface EnhancementConfig<T = any, Obj = Element> {
|
|
57
|
+
export interface EnhancementConfig<T = any, Obj = Element> extends EnhancementConfigBase<T> {
|
|
38
58
|
|
|
39
59
|
spawn: Spawner<T, Obj>;
|
|
40
60
|
|
|
41
61
|
//Applicable to passing in the initVals during the spawn lifecycle event
|
|
42
62
|
withAttrs?: AttrPatterns<T>;
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
allowUnprefixed?: string | RegExp;
|
|
46
|
-
|
|
47
|
-
//keys of type symbol are used for dependency injection
|
|
48
|
-
//and are used by assign-gingerly
|
|
49
|
-
symlinks?: { [key: symbol]: keyof T };
|
|
64
|
+
|
|
50
65
|
//only applicable when spawning from a DOM Element reference
|
|
51
66
|
enhKey?: EnhKey;
|
|
52
|
-
lifecycleKeys?:
|
|
53
|
-
| true // Use standard names: "dispose" method, "resolved" property/event
|
|
54
|
-
| {
|
|
55
|
-
dispose?: string | symbol,
|
|
56
|
-
resolved?: string | symbol
|
|
57
|
-
}
|
|
58
|
-
//used by mount-observer, not by assign-gingerly
|
|
59
|
-
//impossible to polyfill, but will always be disposed
|
|
60
|
-
//when oElement's reference count goes to zero
|
|
61
|
-
disposeOn?: DisposeEvent | DisposeEvent[]
|
|
62
67
|
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -66,6 +71,9 @@ export type Constructor = new (...args: any[]) => any;
|
|
|
66
71
|
|
|
67
72
|
export type pathString = `?.${string}`;
|
|
68
73
|
|
|
74
|
+
export type CustomElementName = string;
|
|
75
|
+
export type CustomElementConstructorStaticMethodName = string;
|
|
76
|
+
|
|
69
77
|
export interface AttrConfig<T = any> {
|
|
70
78
|
/**
|
|
71
79
|
* Type of the property value (JSON-serializable string format)
|
|
@@ -92,12 +100,14 @@ export interface AttrConfig<T = any> {
|
|
|
92
100
|
/**
|
|
93
101
|
* Parser to transform attribute string value
|
|
94
102
|
* - Function: Inline parser function (not JSON serializable)
|
|
95
|
-
* - String: Named parser reference (JSON serializable)
|
|
96
|
-
*
|
|
97
|
-
* - Dot notation: Looks up static method on custom element (e.g., 'my-widget.parseSpecial')
|
|
98
|
-
* Falls back to global registry if custom element not found
|
|
103
|
+
* - String: Named parser reference (JSON serializable) - looks up in global parser registry (e.g., 'timestamp', 'csv')
|
|
104
|
+
* - Tuple: [CustomElementName, StaticMethodName] - looks up static method on custom element constructor (e.g., ['my-widget', 'parseSpecial'])
|
|
99
105
|
*/
|
|
100
|
-
parser?:
|
|
106
|
+
parser?:
|
|
107
|
+
| ((attrValue: string | null) => any)
|
|
108
|
+
| string
|
|
109
|
+
| [CustomElementName, CustomElementConstructorStaticMethodName]
|
|
110
|
+
;
|
|
101
111
|
|
|
102
112
|
/**
|
|
103
113
|
* Default value to use when attribute is missing
|
|
@@ -161,10 +171,20 @@ export interface IAssignGingerlyOptions {
|
|
|
161
171
|
bypassChecks?: boolean;
|
|
162
172
|
}
|
|
163
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Event dispatched when enhancement configs are registered
|
|
176
|
+
*/
|
|
177
|
+
export declare class EnhancementRegisteredEvent extends Event {
|
|
178
|
+
static eventName: string;
|
|
179
|
+
config: EnhancementConfig | EnhancementConfig[];
|
|
180
|
+
constructor(config: EnhancementConfig | EnhancementConfig[]);
|
|
181
|
+
}
|
|
182
|
+
|
|
164
183
|
/**
|
|
165
184
|
* Base registry class for managing enhancement configurations
|
|
185
|
+
* Extends EventTarget to dispatch events when configs are registered
|
|
166
186
|
*/
|
|
167
|
-
export declare class EnhancementRegistry {
|
|
187
|
+
export declare class EnhancementRegistry extends EventTarget {
|
|
168
188
|
private items;
|
|
169
189
|
push(items: EnhancementConfig | EnhancementConfig[]): void;
|
|
170
190
|
getItems(): EnhancementConfig[];
|
|
@@ -172,6 +192,43 @@ export declare class EnhancementRegistry {
|
|
|
172
192
|
findByEnhKey(enhKey: string | symbol): EnhancementConfig | undefined;
|
|
173
193
|
}
|
|
174
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Constructor signature for ItemScope Manager classes
|
|
197
|
+
*/
|
|
198
|
+
export type ItemscopeManager<T = any> = {
|
|
199
|
+
new (element: HTMLElement, initVals?: Partial<T>): T;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Configuration for ItemScope Manager registration
|
|
204
|
+
*/
|
|
205
|
+
export interface ItemscopeManagerConfig<T = any> {
|
|
206
|
+
/**
|
|
207
|
+
* Manager class constructor
|
|
208
|
+
*/
|
|
209
|
+
manager: ItemscopeManager<T>;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Optional lifecycle method keys
|
|
213
|
+
* - dispose: Method name to call when manager is disposed
|
|
214
|
+
* - resolved: Property/event name indicating manager is ready
|
|
215
|
+
*/
|
|
216
|
+
lifecycleKeys?: {
|
|
217
|
+
dispose?: string | symbol;
|
|
218
|
+
resolved?: string | symbol;
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Registry for ItemScope Manager configurations
|
|
224
|
+
* Extends EventTarget to support lazy registration via events
|
|
225
|
+
*/
|
|
226
|
+
export declare class ItemscopeRegistry extends EventTarget {
|
|
227
|
+
define(name: string, config: ItemscopeManagerConfig): void;
|
|
228
|
+
get(name: string): ItemscopeManagerConfig | undefined;
|
|
229
|
+
whenDefined(name: string): Promise<void>;
|
|
230
|
+
}
|
|
231
|
+
|
|
175
232
|
/**
|
|
176
233
|
* Main assignGingerly function
|
|
177
234
|
*/
|
package/types/global.d.ts
CHANGED
package/waitForEvent.js
CHANGED
|
@@ -3,17 +3,31 @@
|
|
|
3
3
|
* @param et - The EventTarget to listen on
|
|
4
4
|
* @param eventName - The event name to wait for (resolves the promise)
|
|
5
5
|
* @param failureEventName - Optional event name that rejects the promise
|
|
6
|
+
* @param timeout - Optional timeout in milliseconds (rejects if exceeded)
|
|
6
7
|
* @returns Promise that resolves with the event
|
|
7
8
|
*/
|
|
8
|
-
export function waitForEvent(et, eventName, failureEventName) {
|
|
9
|
+
export function waitForEvent(et, eventName, failureEventName, timeout) {
|
|
9
10
|
return new Promise((resolve, reject) => {
|
|
11
|
+
let timeoutId;
|
|
12
|
+
const cleanup = () => {
|
|
13
|
+
if (timeoutId !== undefined) {
|
|
14
|
+
clearTimeout(timeoutId);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
10
17
|
et.addEventListener(eventName, (e) => {
|
|
18
|
+
cleanup();
|
|
11
19
|
resolve(e);
|
|
12
20
|
}, { once: true });
|
|
13
21
|
if (failureEventName !== undefined) {
|
|
14
22
|
et.addEventListener(failureEventName, (e) => {
|
|
23
|
+
cleanup();
|
|
15
24
|
reject(e);
|
|
16
25
|
}, { once: true });
|
|
17
26
|
}
|
|
27
|
+
if (timeout !== undefined && timeout > 0) {
|
|
28
|
+
timeoutId = setTimeout(() => {
|
|
29
|
+
reject(new Error(`Timeout waiting for event '${eventName}' after ${timeout}ms`));
|
|
30
|
+
}, timeout);
|
|
31
|
+
}
|
|
18
32
|
});
|
|
19
33
|
}
|
package/waitForEvent.ts
CHANGED
|
@@ -3,17 +3,28 @@
|
|
|
3
3
|
* @param et - The EventTarget to listen on
|
|
4
4
|
* @param eventName - The event name to wait for (resolves the promise)
|
|
5
5
|
* @param failureEventName - Optional event name that rejects the promise
|
|
6
|
+
* @param timeout - Optional timeout in milliseconds (rejects if exceeded)
|
|
6
7
|
* @returns Promise that resolves with the event
|
|
7
8
|
*/
|
|
8
9
|
export function waitForEvent<TEvent extends Event = Event>(
|
|
9
10
|
et: EventTarget,
|
|
10
11
|
eventName: string,
|
|
11
|
-
failureEventName?: string
|
|
12
|
+
failureEventName?: string,
|
|
13
|
+
timeout?: number
|
|
12
14
|
): Promise<TEvent> {
|
|
13
15
|
return new Promise((resolve, reject) => {
|
|
16
|
+
let timeoutId: number | undefined;
|
|
17
|
+
|
|
18
|
+
const cleanup = () => {
|
|
19
|
+
if (timeoutId !== undefined) {
|
|
20
|
+
clearTimeout(timeoutId);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
14
24
|
et.addEventListener(
|
|
15
25
|
eventName,
|
|
16
26
|
(e) => {
|
|
27
|
+
cleanup();
|
|
17
28
|
resolve(e as TEvent);
|
|
18
29
|
},
|
|
19
30
|
{ once: true }
|
|
@@ -23,10 +34,17 @@ export function waitForEvent<TEvent extends Event = Event>(
|
|
|
23
34
|
et.addEventListener(
|
|
24
35
|
failureEventName,
|
|
25
36
|
(e) => {
|
|
37
|
+
cleanup();
|
|
26
38
|
reject(e as TEvent);
|
|
27
39
|
},
|
|
28
40
|
{ once: true }
|
|
29
41
|
);
|
|
30
42
|
}
|
|
43
|
+
|
|
44
|
+
if (timeout !== undefined && timeout > 0) {
|
|
45
|
+
timeoutId = setTimeout(() => {
|
|
46
|
+
reject(new Error(`Timeout waiting for event '${eventName}' after ${timeout}ms`));
|
|
47
|
+
}, timeout) as unknown as number;
|
|
48
|
+
}
|
|
31
49
|
});
|
|
32
50
|
}
|