assign-gingerly 0.0.12 → 0.0.14

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/assignGingerly.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- import { EnhancementConfig } from "./types";
3
+ import { EnhancementConfig } from "./types/assign-gingerly/types";
4
4
 
5
5
  /**
6
6
  * @deprecated Use EnhancementConfig instead
package/index.js CHANGED
@@ -2,4 +2,6 @@ export { assignGingerly } from './assignGingerly.js';
2
2
  export { assignTentatively } from './assignTentatively.js';
3
3
  export { BaseRegistry } from './assignGingerly.js';
4
4
  export { waitForEvent } from './waitForEvent.js';
5
+ export { ParserRegistry, globalParserRegistry } from './parserRegistry.js';
6
+ export { parseWithAttrs } from './parseWithAttrs.js';
5
7
  import './object-extension.js';
package/index.ts CHANGED
@@ -2,4 +2,6 @@ export {assignGingerly} from './assignGingerly.js';
2
2
  export {assignTentatively} from './assignTentatively.js';
3
3
  export {BaseRegistry} from './assignGingerly.js';
4
4
  export {waitForEvent} from './waitForEvent.js';
5
+ export {ParserRegistry, globalParserRegistry} from './parserRegistry.js';
6
+ export {parseWithAttrs} from './parseWithAttrs.js';
5
7
  import './object-extension.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
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": {
@@ -13,28 +13,36 @@
13
13
  "license": "MIT",
14
14
  "author": "Bruce B. Anderson <andeson.bruce.b@gmail.com>",
15
15
  "type": "module",
16
- "types": "types.d.ts",
16
+ "types": "types/assign-gingerly/types.d.ts",
17
17
  "files": [
18
18
  "*.js",
19
19
  "*.ts",
20
20
  "README.md",
21
- "LICENSE"
21
+ "LICENSE",
22
+ "types/assign-gingerly/types.d.ts"
22
23
  ],
23
24
  "exports": {
24
25
  ".": {
25
26
  "default": "./index.js",
26
- "types": "./types.d.ts"
27
+ "types": "./index.ts"
27
28
  },
28
29
  "./assignGingerly.js": {
29
30
  "default": "./assignGingerly.js",
30
- "types": "./types.d.ts"
31
+ "types": "./assignGingerly.ts"
31
32
  },
32
33
  "./assignTentatively.js": {
33
34
  "default": "./assignTentatively.js",
34
- "types": "./types.d.ts"
35
+ "types": "./assignTentatively.ts"
35
36
  },
36
37
  "./waitForEvent.js": {
37
38
  "default": "./waitForEvent.js"
39
+ },
40
+ "./parserRegistry.js": {
41
+ "default": "./parserRegistry.js"
42
+ },
43
+ "./parseWithAttrs.js": {
44
+ "default": "./parseWithAttrs.js",
45
+ "types": "./parseWithAttrs.ts"
38
46
  }
39
47
  },
40
48
  "main": "index.js",
package/parseWithAttrs.js CHANGED
@@ -1,3 +1,121 @@
1
+ import { globalParserRegistry } from './parserRegistry.js';
2
+ // Module-level cache for parsed attribute values
3
+ // Structure: Map<configKey, Map<attrValue, parsedValue>>
4
+ const parseCache = new Map();
5
+ /**
6
+ * Resolves a parser specification to an actual parser function
7
+ * Supports:
8
+ * - Inline functions (direct use)
9
+ * - Named parsers from global registry
10
+ * - Custom element static methods (element-name.methodName)
11
+ *
12
+ * @param parserSpec - Parser function or string reference
13
+ * @returns The resolved parser function
14
+ * @throws Error if parser cannot be resolved
15
+ */
16
+ function resolveParser(parserSpec) {
17
+ // Undefined - no parser specified
18
+ if (parserSpec === undefined) {
19
+ return undefined;
20
+ }
21
+ // Inline function - use directly
22
+ if (typeof parserSpec === 'function') {
23
+ return parserSpec;
24
+ }
25
+ // String reference - resolve it
26
+ if (typeof parserSpec === 'string') {
27
+ // Check if it's a custom element reference (contains dot)
28
+ if (parserSpec.includes('.')) {
29
+ const dotIndex = parserSpec.indexOf('.');
30
+ const elementName = parserSpec.substring(0, dotIndex);
31
+ const methodName = parserSpec.substring(dotIndex + 1);
32
+ // Try custom element lookup
33
+ if (typeof customElements !== 'undefined') {
34
+ try {
35
+ const ctr = customElements.get(elementName);
36
+ if (ctr && typeof ctr[methodName] === 'function') {
37
+ return ctr[methodName];
38
+ }
39
+ }
40
+ catch (e) {
41
+ // customElements.get might throw, fall through to registry
42
+ }
43
+ }
44
+ // Fall through to global registry (allows dot notation in registry too)
45
+ }
46
+ // Try global registry
47
+ const parser = globalParserRegistry.get(parserSpec);
48
+ if (parser) {
49
+ return parser;
50
+ }
51
+ // Not found anywhere
52
+ throw new Error(`Parser "${parserSpec}" not found. ` +
53
+ `Check that it's registered in globalParserRegistry or exists as a static method on the custom element.`);
54
+ }
55
+ return undefined;
56
+ }
57
+ /**
58
+ * Creates a cache key from an AttrConfig
59
+ * Includes instanceOf and parser identifier to ensure correct cache hits
60
+ */
61
+ function getCacheKey(config) {
62
+ const instanceOfStr = typeof config.instanceOf === 'function'
63
+ ? config.instanceOf.name
64
+ : (config.instanceOf || 'default');
65
+ // Include parser in cache key
66
+ let parserStr;
67
+ if (config.parser === undefined) {
68
+ parserStr = 'builtin';
69
+ }
70
+ else if (typeof config.parser === 'string') {
71
+ parserStr = `named:${config.parser}`;
72
+ }
73
+ else {
74
+ parserStr = 'custom';
75
+ }
76
+ return `${instanceOfStr}|${parserStr}`;
77
+ }
78
+ /**
79
+ * Gets a cached parsed value or parses and caches it
80
+ * @param attrValue - The attribute value to parse (or null)
81
+ * @param config - The attribute configuration
82
+ * @param parser - The parser function to use
83
+ * @returns The parsed value
84
+ */
85
+ function parseWithCache(attrValue, config, parser) {
86
+ // Skip caching for Boolean (presence check doesn't benefit from caching)
87
+ if (config.instanceOf === 'Boolean') {
88
+ return parser(attrValue);
89
+ }
90
+ // Get or create cache for this config
91
+ const cacheKey = getCacheKey(config);
92
+ if (!parseCache.has(cacheKey)) {
93
+ parseCache.set(cacheKey, new Map());
94
+ }
95
+ const valueCache = parseCache.get(cacheKey);
96
+ // Use special key for null values
97
+ const valueCacheKey = attrValue === null ? '__NULL__' : attrValue;
98
+ // Check if we have a cached value
99
+ if (valueCache.has(valueCacheKey)) {
100
+ const cachedValue = valueCache.get(valueCacheKey);
101
+ // Return clone if requested
102
+ if (config.parseCache === 'cloned') {
103
+ // Use structuredClone for deep cloning
104
+ return structuredClone(cachedValue);
105
+ }
106
+ // Return shared reference
107
+ return cachedValue;
108
+ }
109
+ // Parse the value
110
+ const parsedValue = parser(attrValue);
111
+ // Store in cache
112
+ valueCache.set(valueCacheKey, parsedValue);
113
+ // Return clone if requested (even on first parse)
114
+ if (config.parseCache === 'cloned') {
115
+ return structuredClone(parsedValue);
116
+ }
117
+ return parsedValue;
118
+ }
1
119
  /**
2
120
  * Checks if a string contains a dash or non-ASCII character
3
121
  */
@@ -188,14 +306,40 @@ export function parseWithAttrs(element, attrPatterns, allowUnprefixed) {
188
306
  // Second pass: read attributes and parse values
189
307
  for (const [key, { attrName, config }] of resolvedAttrs) {
190
308
  const attrValue = getAttributeValue(element, attrName, allowUnprefixed);
191
- // Skip if attribute doesn't exist
192
- if (attrValue === null && config.instanceOf !== 'Boolean') {
309
+ // Handle missing attribute
310
+ if (attrValue === null) {
311
+ // Use valIfNull if defined
312
+ if (config.valIfNull !== undefined) {
313
+ const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
314
+ if (mapsTo === '.') {
315
+ // Spread into root
316
+ if (typeof config.valIfNull === 'object' && config.valIfNull !== null) {
317
+ Object.assign(result, config.valIfNull);
318
+ }
319
+ }
320
+ else {
321
+ result[mapsTo] = config.valIfNull;
322
+ }
323
+ }
324
+ // Skip if no valIfNull and not Boolean (Boolean uses presence check)
325
+ else if (config.instanceOf !== 'Boolean') {
326
+ continue;
327
+ }
328
+ // For Boolean without valIfNull, fall through to parser
329
+ else {
330
+ const parser = resolveParser(config.parser) || getDefaultParser(config.instanceOf);
331
+ const parsedValue = parser(attrValue);
332
+ const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
333
+ result[mapsTo] = parsedValue;
334
+ }
193
335
  continue;
194
336
  }
195
- // Get parser
196
- const parser = config.parser || getDefaultParser(config.instanceOf);
197
- // Parse value
198
- const parsedValue = parser(attrValue);
337
+ // Attribute exists - parse normally
338
+ const parser = resolveParser(config.parser) || getDefaultParser(config.instanceOf);
339
+ // Use cache if parseCache is specified
340
+ const parsedValue = config.parseCache
341
+ ? parseWithCache(attrValue, config, parser)
342
+ : parser(attrValue);
199
343
  // Determine target property
200
344
  const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
201
345
  // Add to result
package/parseWithAttrs.ts CHANGED
@@ -1,4 +1,148 @@
1
- import { AttrPatterns, AttrConfig } from './types';
1
+ import { AttrPatterns, AttrConfig } from './types/assign-gingerly/types';
2
+ import { globalParserRegistry } from './parserRegistry.js';
3
+
4
+ // Module-level cache for parsed attribute values
5
+ // Structure: Map<configKey, Map<attrValue, parsedValue>>
6
+ const parseCache = new Map<string, Map<string, any>>();
7
+
8
+ /**
9
+ * Resolves a parser specification to an actual parser function
10
+ * Supports:
11
+ * - Inline functions (direct use)
12
+ * - Named parsers from global registry
13
+ * - Custom element static methods (element-name.methodName)
14
+ *
15
+ * @param parserSpec - Parser function or string reference
16
+ * @returns The resolved parser function
17
+ * @throws Error if parser cannot be resolved
18
+ */
19
+ function resolveParser(parserSpec: ((v: string | null) => any) | string | undefined): ((v: string | null) => any) | undefined {
20
+ // Undefined - no parser specified
21
+ if (parserSpec === undefined) {
22
+ return undefined;
23
+ }
24
+
25
+ // Inline function - use directly
26
+ if (typeof parserSpec === 'function') {
27
+ return parserSpec;
28
+ }
29
+
30
+ // String reference - resolve it
31
+ if (typeof parserSpec === 'string') {
32
+ // Check if it's a custom element reference (contains dot)
33
+ if (parserSpec.includes('.')) {
34
+ const dotIndex = parserSpec.indexOf('.');
35
+ const elementName = parserSpec.substring(0, dotIndex);
36
+ const methodName = parserSpec.substring(dotIndex + 1);
37
+
38
+ // Try custom element lookup
39
+ if (typeof customElements !== 'undefined') {
40
+ try {
41
+ const ctr = customElements.get(elementName);
42
+ if (ctr && typeof (ctr as any)[methodName] === 'function') {
43
+ return (ctr as any)[methodName];
44
+ }
45
+ } catch (e) {
46
+ // customElements.get might throw, fall through to registry
47
+ }
48
+ }
49
+
50
+ // Fall through to global registry (allows dot notation in registry too)
51
+ }
52
+
53
+ // Try global registry
54
+ const parser = globalParserRegistry.get(parserSpec);
55
+ if (parser) {
56
+ return parser;
57
+ }
58
+
59
+ // Not found anywhere
60
+ throw new Error(
61
+ `Parser "${parserSpec}" not found. ` +
62
+ `Check that it's registered in globalParserRegistry or exists as a static method on the custom element.`
63
+ );
64
+ }
65
+
66
+ return undefined;
67
+ }
68
+
69
+ /**
70
+ * Creates a cache key from an AttrConfig
71
+ * Includes instanceOf and parser identifier to ensure correct cache hits
72
+ */
73
+ function getCacheKey(config: AttrConfig<any>): string {
74
+ const instanceOfStr = typeof config.instanceOf === 'function'
75
+ ? config.instanceOf.name
76
+ : (config.instanceOf || 'default');
77
+
78
+ // Include parser in cache key
79
+ let parserStr: string;
80
+ if (config.parser === undefined) {
81
+ parserStr = 'builtin';
82
+ } else if (typeof config.parser === 'string') {
83
+ parserStr = `named:${config.parser}`;
84
+ } else {
85
+ parserStr = 'custom';
86
+ }
87
+
88
+ return `${instanceOfStr}|${parserStr}`;
89
+ }
90
+
91
+ /**
92
+ * Gets a cached parsed value or parses and caches it
93
+ * @param attrValue - The attribute value to parse (or null)
94
+ * @param config - The attribute configuration
95
+ * @param parser - The parser function to use
96
+ * @returns The parsed value
97
+ */
98
+ function parseWithCache(
99
+ attrValue: string | null,
100
+ config: AttrConfig<any>,
101
+ parser: (v: string | null) => any
102
+ ): any {
103
+ // Skip caching for Boolean (presence check doesn't benefit from caching)
104
+ if (config.instanceOf === 'Boolean') {
105
+ return parser(attrValue);
106
+ }
107
+
108
+ // Get or create cache for this config
109
+ const cacheKey = getCacheKey(config);
110
+ if (!parseCache.has(cacheKey)) {
111
+ parseCache.set(cacheKey, new Map());
112
+ }
113
+
114
+ const valueCache = parseCache.get(cacheKey)!;
115
+
116
+ // Use special key for null values
117
+ const valueCacheKey = attrValue === null ? '__NULL__' : attrValue;
118
+
119
+ // Check if we have a cached value
120
+ if (valueCache.has(valueCacheKey)) {
121
+ const cachedValue = valueCache.get(valueCacheKey);
122
+
123
+ // Return clone if requested
124
+ if (config.parseCache === 'cloned') {
125
+ // Use structuredClone for deep cloning
126
+ return structuredClone(cachedValue);
127
+ }
128
+
129
+ // Return shared reference
130
+ return cachedValue;
131
+ }
132
+
133
+ // Parse the value
134
+ const parsedValue = parser(attrValue);
135
+
136
+ // Store in cache
137
+ valueCache.set(valueCacheKey, parsedValue);
138
+
139
+ // Return clone if requested (even on first parse)
140
+ if (config.parseCache === 'cloned') {
141
+ return structuredClone(parsedValue);
142
+ }
143
+
144
+ return parsedValue;
145
+ }
2
146
 
3
147
  /**
4
148
  * Checks if a string contains a dash or non-ASCII character
@@ -221,16 +365,42 @@ export function parseWithAttrs<T = any>(
221
365
  for (const [key, { attrName, config }] of resolvedAttrs) {
222
366
  const attrValue = getAttributeValue(element, attrName, allowUnprefixed);
223
367
 
224
- // Skip if attribute doesn't exist
225
- if (attrValue === null && config.instanceOf !== 'Boolean') {
368
+ // Handle missing attribute
369
+ if (attrValue === null) {
370
+ // Use valIfNull if defined
371
+ if (config.valIfNull !== undefined) {
372
+ const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
373
+
374
+ if (mapsTo === '.') {
375
+ // Spread into root
376
+ if (typeof config.valIfNull === 'object' && config.valIfNull !== null) {
377
+ Object.assign(result, config.valIfNull);
378
+ }
379
+ } else {
380
+ result[mapsTo as string] = config.valIfNull;
381
+ }
382
+ }
383
+ // Skip if no valIfNull and not Boolean (Boolean uses presence check)
384
+ else if (config.instanceOf !== 'Boolean') {
385
+ continue;
386
+ }
387
+ // For Boolean without valIfNull, fall through to parser
388
+ else {
389
+ const parser = resolveParser(config.parser) || getDefaultParser(config.instanceOf);
390
+ const parsedValue = parser(attrValue);
391
+ const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
392
+ result[mapsTo as string] = parsedValue;
393
+ }
226
394
  continue;
227
395
  }
228
396
 
229
- // Get parser
230
- const parser = config.parser || getDefaultParser(config.instanceOf);
397
+ // Attribute exists - parse normally
398
+ const parser = resolveParser(config.parser) || getDefaultParser(config.instanceOf);
231
399
 
232
- // Parse value
233
- const parsedValue = parser(attrValue);
400
+ // Use cache if parseCache is specified
401
+ const parsedValue = config.parseCache
402
+ ? parseWithCache(attrValue, config, parser)
403
+ : parser(attrValue);
234
404
 
235
405
  // Determine target property
236
406
  const mapsTo = config.mapsTo ?? (key === 'base' ? '.' : key);
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Registry for named parsers that can be referenced by string name
3
+ * Enables JSON serialization of configs with custom parsers
4
+ */
5
+ export class ParserRegistry {
6
+ parsers = new Map();
7
+ /**
8
+ * Register a parser with a given name
9
+ * @param name - The name to register the parser under
10
+ * @param parser - The parser function
11
+ */
12
+ register(name, parser) {
13
+ if (this.parsers.has(name)) {
14
+ console.warn(`Parser "${name}" already registered, overwriting`);
15
+ }
16
+ this.parsers.set(name, parser);
17
+ }
18
+ /**
19
+ * Get a parser by name
20
+ * @param name - The name of the parser
21
+ * @returns The parser function or undefined if not found
22
+ */
23
+ get(name) {
24
+ return this.parsers.get(name);
25
+ }
26
+ /**
27
+ * Check if a parser is registered
28
+ * @param name - The name to check
29
+ * @returns True if the parser exists
30
+ */
31
+ has(name) {
32
+ return this.parsers.has(name);
33
+ }
34
+ /**
35
+ * Unregister a parser
36
+ * @param name - The name of the parser to remove
37
+ * @returns True if the parser was removed, false if it didn't exist
38
+ */
39
+ unregister(name) {
40
+ return this.parsers.delete(name);
41
+ }
42
+ /**
43
+ * Get all registered parser names
44
+ * @returns Array of parser names
45
+ */
46
+ getNames() {
47
+ return Array.from(this.parsers.keys());
48
+ }
49
+ }
50
+ /**
51
+ * Global parser registry instance
52
+ * Use this to register parsers that can be referenced by name in configs
53
+ */
54
+ export const globalParserRegistry = new ParserRegistry();
55
+ // Register common built-in parsers
56
+ globalParserRegistry.register('timestamp', (v) => v ? new Date(v).getTime() : null);
57
+ globalParserRegistry.register('date', (v) => v ? new Date(v) : null);
58
+ globalParserRegistry.register('csv', (v) => v ? v.split(',').map(s => s.trim()) : []);
59
+ globalParserRegistry.register('int', (v) => v ? parseInt(v, 10) : null);
60
+ globalParserRegistry.register('float', (v) => v ? parseFloat(v) : null);
61
+ globalParserRegistry.register('boolean', (v) => v !== null);
62
+ globalParserRegistry.register('json', (v) => {
63
+ if (v === null || v === '')
64
+ return null;
65
+ try {
66
+ return JSON.parse(v);
67
+ }
68
+ catch (e) {
69
+ throw new Error(`Failed to parse JSON: "${v}". Error: ${e}`);
70
+ }
71
+ });
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Registry for named parsers that can be referenced by string name
3
+ * Enables JSON serialization of configs with custom parsers
4
+ */
5
+ export class ParserRegistry {
6
+ private parsers = new Map<string, (v: string | null) => any>();
7
+
8
+ /**
9
+ * Register a parser with a given name
10
+ * @param name - The name to register the parser under
11
+ * @param parser - The parser function
12
+ */
13
+ register(name: string, parser: (v: string | null) => any): void {
14
+ if (this.parsers.has(name)) {
15
+ console.warn(`Parser "${name}" already registered, overwriting`);
16
+ }
17
+ this.parsers.set(name, parser);
18
+ }
19
+
20
+ /**
21
+ * Get a parser by name
22
+ * @param name - The name of the parser
23
+ * @returns The parser function or undefined if not found
24
+ */
25
+ get(name: string): ((v: string | null) => any) | undefined {
26
+ return this.parsers.get(name);
27
+ }
28
+
29
+ /**
30
+ * Check if a parser is registered
31
+ * @param name - The name to check
32
+ * @returns True if the parser exists
33
+ */
34
+ has(name: string): boolean {
35
+ return this.parsers.has(name);
36
+ }
37
+
38
+ /**
39
+ * Unregister a parser
40
+ * @param name - The name of the parser to remove
41
+ * @returns True if the parser was removed, false if it didn't exist
42
+ */
43
+ unregister(name: string): boolean {
44
+ return this.parsers.delete(name);
45
+ }
46
+
47
+ /**
48
+ * Get all registered parser names
49
+ * @returns Array of parser names
50
+ */
51
+ getNames(): string[] {
52
+ return Array.from(this.parsers.keys());
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Global parser registry instance
58
+ * Use this to register parsers that can be referenced by name in configs
59
+ */
60
+ export const globalParserRegistry = new ParserRegistry();
61
+
62
+ // Register common built-in parsers
63
+ globalParserRegistry.register('timestamp', (v) =>
64
+ v ? new Date(v).getTime() : null
65
+ );
66
+
67
+ globalParserRegistry.register('date', (v) =>
68
+ v ? new Date(v) : null
69
+ );
70
+
71
+ globalParserRegistry.register('csv', (v) =>
72
+ v ? v.split(',').map(s => s.trim()) : []
73
+ );
74
+
75
+ globalParserRegistry.register('int', (v) =>
76
+ v ? parseInt(v, 10) : null
77
+ );
78
+
79
+ globalParserRegistry.register('float', (v) =>
80
+ v ? parseFloat(v) : null
81
+ );
82
+
83
+ globalParserRegistry.register('boolean', (v) =>
84
+ v !== null
85
+ );
86
+
87
+ globalParserRegistry.register('json', (v) => {
88
+ if (v === null || v === '') return null;
89
+ try {
90
+ return JSON.parse(v);
91
+ } catch (e) {
92
+ throw new Error(`Failed to parse JSON: "${v}". Error: ${e}`);
93
+ }
94
+ });
package/types/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bruce B. Anderson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ # types
2
+ Common Shared Type Definitions
@@ -88,9 +88,29 @@ export interface AttrConfig<T = any> {
88
88
  | `${pathString} -=`
89
89
 
90
90
  /**
91
- * Optional parser function to transform attribute string value
91
+ * Parser to transform attribute string value
92
+ * - Function: Inline parser function (not JSON serializable)
93
+ * - String: Named parser reference (JSON serializable)
94
+ * - Simple name: Looks up in global parser registry (e.g., 'timestamp', 'csv')
95
+ * - Dot notation: Looks up static method on custom element (e.g., 'my-widget.parseSpecial')
96
+ * Falls back to global registry if custom element not found
92
97
  */
93
- parser?: (attrValue: string | null) => any;
98
+ parser?: ((attrValue: string | null) => any) | string;
99
+
100
+ /**
101
+ * Default value to use when attribute is missing
102
+ * If defined, bypasses parser when attribute is not present
103
+ * If undefined, property is not added to initVals when attribute is missing
104
+ */
105
+ valIfNull?: any;
106
+
107
+ /**
108
+ * Enable caching of parsed attribute values
109
+ * - 'shared': Cache and reuse the same parsed object (fast, but enhancements must not mutate)
110
+ * - 'cloned': Cache and return a structural clone (safer, but slower)
111
+ * Note: Parsers should be pure functions when using caching
112
+ */
113
+ parseCache?: 'shared' | 'cloned';
94
114
 
95
115
  // /**
96
116
  // * Whether to only read the initial value (true) or continue observing changes (false)