assign-gingerly 0.0.78 → 0.0.80

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.
@@ -224,7 +224,7 @@ export class ManageTemplateListHandler {
224
224
  if (fragment.childNodes.length > 0) {
225
225
  const waitOpt = resolvedParams.waitForSettled;
226
226
  if (waitOpt) {
227
- const { waitForSettled } = await import('../waitForSettled.js');
227
+ const { waitForSettled } = await import('../utils/waitForSettled.js');
228
228
  const idleMs = typeof waitOpt === 'object' ? waitOpt.idleMs : 100;
229
229
  const timeout = typeof waitOpt === 'object' ? waitOpt.timeout : undefined;
230
230
  try {
@@ -263,7 +263,7 @@ export class ManageTemplateListHandler implements AssignFromHandler {
263
263
  if (fragment.childNodes.length > 0) {
264
264
  const waitOpt = resolvedParams.waitForSettled;
265
265
  if (waitOpt) {
266
- const { waitForSettled } = await import('../waitForSettled.js');
266
+ const { waitForSettled } = await import('../utils/waitForSettled.js');
267
267
  const idleMs = typeof waitOpt === 'object' ? waitOpt.idleMs : 100;
268
268
  const timeout = typeof waitOpt === 'object' ? waitOpt.timeout : undefined;
269
269
  try {
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { assignGingerly } from './assignGingerly.js';
2
2
  export { assignTentatively } from './assignTentatively.js';
3
3
  export { EnhancementRegistry, ItemscopeRegistry, EnhancementRegisteredEvent } from './assignGingerly.js';
4
- export { waitForEvent } from './waitForEvent.js';
4
+ export { waitForEvent } from './utils/waitForEvent.js';
5
5
  export { ParserRegistry, globalParserRegistry } from './parserRegistry.js';
6
6
  export { parseWithAttrs } from './parseWithAttrs.js';
7
7
  export { buildCSSQuery } from './buildCSSQuery.js';
package/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export {assignGingerly} from './assignGingerly.js';
2
2
  export {assignTentatively} from './assignTentatively.js';
3
3
  export {EnhancementRegistry, ItemscopeRegistry, EnhancementRegisteredEvent} from './assignGingerly.js';
4
- export {waitForEvent} from './waitForEvent.js';
4
+ export {waitForEvent} from './utils/waitForEvent.js';
5
5
  export {ParserRegistry, globalParserRegistry} from './parserRegistry.js';
6
6
  export {parseWithAttrs} from './parseWithAttrs.js';
7
7
  export {buildCSSQuery} from './buildCSSQuery.js';
@@ -8,9 +8,10 @@ This document provides step-by-step instructions for creating a **brand new** cu
8
8
  - Custom element features (composable behavior classes injected into elements) — see [NewCustomElementFeature.md](./NewCustomElementFeature.md)
9
9
  - Enhancements (declarative behaviors attached to existing elements via attributes) — see [NewEnhancementInstructions.md](./NewEnhancementInstructions.md)
10
10
 
11
- ## Reference Implementation
11
+ ## Reference Implementations
12
12
 
13
13
  - **[time-ticker](https://github.com/bahrus/time-ticker)** — A non-visual custom element that fires events periodically. Demonstrates extending `ElementMaker`, a custom feature (`TimeTicker`), roundabout wiring via `defRef.json`, and the `def.js` / `wireFeatures.js` pattern.
14
+ - **[scratch-box](https://github.com/bahrus/scratch-box)** — A visual, form-associated custom element with a declarative shadow DOM template and zero custom element JavaScript. Demonstrates `cede` script definition from a static `root.html` and JSON feature configuration.
14
15
 
15
16
  ## Prerequisites
16
17
 
@@ -51,7 +52,9 @@ This document provides step-by-step instructions for creating a **brand new** cu
51
52
  },
52
53
  "dependencies": {
53
54
  "assign-gingerly": "0.0.48",
54
- "el-maker": "0.0.0"
55
+ "el-maker": "0.0.0",
56
+ "imp-h": "0.0.5",
57
+ "mount-observer": "0.1.50"
55
58
  }
56
59
  }
57
60
  ```
@@ -92,7 +95,12 @@ export type T = AllProps;
92
95
  - `AllProps` — includes internal/computed state managed by roundabout
93
96
  - Export `T` as a convenience alias for use in `defRef.mjs` type annotations
94
97
 
95
- ## Step 4: Create the Element Class
98
+ ## Step 4: Create the Element Class, if the complexity is too much for a "code-free" solution.
99
+
100
+ For visual web components that use declarative Shadow DOM, step 4 should be considered as a last resort, after exausting:
101
+
102
+ 1. The power of assign-gingerly/assignFrom/RoundaboutLib configuration (JSON)
103
+ 2. Defining a new reusable custom element feature to include with el-maker's package (Step 5).
96
104
 
97
105
  Create `[element-name]-element.js` (e.g., `my-element-element.js`):
98
106
 
@@ -375,9 +383,176 @@ Plus infrastructure:
375
383
 
376
384
  For elements like `time-ticker` that have no HTML template or shadow DOM, simply don't activate the `templateMaker` feature in `wireFeatures.js`. The feature remains declared in `supportedFeatures` (inherited from `ElementMaker`) but is never instantiated because no `assignFeatures` call references it.
377
385
 
378
- ## Elements With HTML (Coming Soon)
386
+ ## Elements With HTML: Declarative Shadow DOM Without a JS Class
387
+
388
+ For visual elements you can skip the `def.js` / `wireFeatures.js` / custom element class entirely and register the element with a `cede` script that extends `el-maker`. The scratch-box checkbox is the reference implementation of this pattern: it is built from a single static HTML file (`root.html`) and a JSON feature configuration (`el-maker.json`).
389
+
390
+ ### How scratch-box is structured
391
+
392
+ | File | Role |
393
+ |------|------|
394
+ | `root.html` | Declarative shadow DOM template, styles, inner form, and enhancement metadata. |
395
+ | `el-maker.mjs` | Type-checked configuration generator for the ElementMaker features. |
396
+ | `el-maker.json` | Generated JSON consumed by the `cede` script. |
397
+
398
+ ### The template file (`root.html`)
399
+
400
+ The host element declares its shadow root declaratively, then contains everything needed inside the shadow DOM, including styles, a form element, and a `<be-hive>` block that wires up declarative enhancements:
401
+
402
+ ```html
403
+ <scratch-box>
404
+ <template shadowrootmode=open>
405
+ <style adopt>
406
+ :host[hidden] { display:none; }
407
+ :host { display:block; background-color: HSL(250, 22%, 41%); padding: 1vw; }
408
+ /* ... remaining styles ... */
409
+ </style>
410
+ <form class="checkbox-wrapper">
411
+ <input 🪢 name=value type="checkbox" id="option"/>
412
+ <link itemprop=value>
413
+ <label for="option">
414
+ <slot name="labelTxt">scratch-box</slot>
415
+ <svg viewBox="0 0 60 40" aria-hidden="true" focusable="false">
416
+ <path d="M21,2 ..." stroke-width="4" fill="none" stroke-dasharray="270" stroke-dashoffset="270"></path>
417
+ </svg>
418
+ </label>
419
+ </form>
420
+
421
+ <be-hive>
422
+ <script type=emc-parser
423
+ src="be-hive/parsers/parse-grouped-capture-statements.js"
424
+ parser-name=parse-grouped-capture-statements></script>
425
+ <script type=emc
426
+ src="be-bound/🪢.json"
427
+ wait-for-parsers=parse-grouped-capture-statements></script>
428
+ </be-hive>
429
+ </template>
430
+ </scratch-box>
431
+ ```
432
+
433
+ Key details:
434
+
435
+ - `shadowrootmode=open` gives the element a declarative shadow DOM that the browser attaches before any script runs.
436
+ - The internal checkbox is named `value` and carries the `🪢` emoji attribute. That marks it for the `be-bound` enhancement so the host `value` property and the inner checkbox `checked` state stay in sync.
437
+ - `<link itemprop=value>` lets the `faceUp` feature expose the element as a form-associated value without any JS wiring.
438
+ - The `<slot name="labelTxt">` lets users provide the label from light DOM via `<span slot="labelTxt">...</span>`.
439
+ - `<style adopt>` with `adopt` ensures the styles are adopted into the shadow root instead of a separate `<style>` element.
440
+
441
+ ### The feature configuration (`el-maker.mjs` → `el-maker.json`)
442
+
443
+ Instead of `wireFeatures.js`, the features are declared in JSON and consumed by the `cede` script. The source file is type-checked TypeScript via JSDoc comments and outputs `el-maker.json`:
444
+
445
+ ```javascript
446
+ //@ts-check
447
+
448
+ import { writeFileSync } from 'fs';
449
+ import { fileURLToPath } from 'url';
450
+ import {akaMethods as m, aka, builtInEmoji} from 'assign-gingerly/DX/emojis.js';
451
+
452
+ /** @import {FontFaceFeatureConfig} from './types/font-face-feature/types'; */
453
+ /** @import {EndUserProps} from './types'; */
454
+ /** @import {RoundaboutOptions} from './types/roundabout/types' */
455
+ /** @import {ElMakerConfig} from './types/el-maker/types' */
456
+
457
+ const props = {
458
+ value: 'value',
459
+ name: 'name',
460
+ disabled: 'disabled',
461
+ };
462
+
463
+ const fontFaceFeatureConfig = {
464
+ fontFamilies: [
465
+ {
466
+ name: 'Indie Flower',
467
+ url: 'https://fonts.gstatic.com/s/indieflower/v24/m8JVjfNVeKWVnh3QMuKkFcZVZ0uH5dI.woff2',
468
+ descriptors: {
469
+ style: 'normal',
470
+ weight: '400',
471
+ unicodeRange: '...',
472
+ },
473
+ },
474
+ // additional font-face descriptors...
475
+ ],
476
+ };
477
+
478
+ const raConfig = {
479
+ assignOptions: {
480
+ akaMethods: {
481
+ '🔍': m['🔍']
482
+ }
483
+ },
484
+ merges: [
485
+ {
486
+ ifKeyIn: ['disabled'],
487
+ assign: {
488
+ '?.shadowRoot?.🔍?.input?.disabled': '?.disabled',
489
+ }
490
+ },
491
+ ],
492
+ };
493
+
494
+ /** @type {ElMakerConfig<EndUserProps>} */
495
+ const features = {
496
+ assignFeatures: {
497
+ faceUp: { customData: { integrateWithRoundabout: true } },
498
+ truthSourcer: {},
499
+ roundabout: { customData: { raConfig } },
500
+ fontMgr: { customData: { fontFaceFeatureConfig } },
501
+ templateMaker: {},
502
+ },
503
+ };
504
+
505
+ export function render() {
506
+ return JSON.stringify(features, null, 4);
507
+ }
508
+
509
+ const __filename = fileURLToPath(import.meta.url);
510
+ const outputFile = __filename.replace(/\.mjs$/, '.json');
511
+ writeFileSync(outputFile, render(), 'utf8');
512
+ ```
513
+
514
+ Run `node el-maker.mjs` (or `npm run build-el-maker` if your `package.json` includes a watch script) to regenerate `el-maker.json`.
515
+
516
+ ### Registering the element in a page
517
+
518
+ The element is defined by a `cede` script. Include `imp-h` (or another template importer) to fetch `root.html`, and `el-maker/def.js` to provide the base class machinery:
519
+
520
+ ```html
521
+ <!DOCTYPE html>
522
+ <html lang="en">
523
+ <head>
524
+ <meta charset="UTF-8">
525
+ <title>scratch-box demo</title>
526
+ <script type=module>
527
+ import 'be-hive/be-hive.js';
528
+ import 'imp-h/imp-h.js';
529
+ import 'el-maker/def.js';
530
+ </script>
531
+ </head>
532
+ <body>
533
+ <scratch-box imp-h="scratch-box/root.html">
534
+ <span slot=labelTxt>Create demo</span>
535
+ <script type=cede data-extends=el-maker src="scratch-box/el-maker.json"></script>
536
+ </scratch-box>
537
+ </body>
538
+ </html>
539
+ ```
540
+
541
+ Notes:
542
+
543
+ - `imp-h` observes the `imp-h` attribute and imports the declarative shadow DOM template from `root.html`.
544
+ - The `<script type=cede data-extends=el-maker>` tells the mount observer to register the host element by extending `ElementMaker` and applying the feature JSON.
545
+ - No JS class file is required because all behavior is provided by the configured ElementMaker features and the declarative shadow DOM.
546
+
547
+ ### When to use this pattern
548
+
549
+ Use the declarative shadow DOM + `cede` pattern when:
550
+
551
+ - The element is primarily visual and static.
552
+ - You want server-side rendering and progressive enhancement with no client-side custom element class.
553
+ - Feature configuration (form association, attribute reflection, reactive wiring, fonts) is sufficient for all behavior.
379
554
 
380
- For elements that render static or dynamic HTML, the `templateMaker` feature handles template instantiation and shadow DOM attachment. Documentation for this pattern including how to declare templates, bind data, and integrate with roundabout — will be added in a future update.
555
+ When you need custom runtime behavior beyond the shared features, fall back to the class-based pattern in the earlier sections and add your own feature.
381
556
 
382
557
  ## Tips
383
558
 
@@ -955,7 +955,8 @@ export interface RestrictedPropSetting {
955
955
  */
956
956
  export interface RestrictedMethodConfig {
957
957
  method: string;
958
- addArgs?: string[]; // Phase V: append sanitizer args
958
+ appendArgs?: string[]; // Phase II: append args to each method call
959
+ addArgs?: string[]; // Deprecated alias for appendArgs
959
960
  }
960
961
 
961
962
  /**
@@ -982,6 +983,8 @@ export interface AssignPermissions {
982
983
  /** Sanitizer options (Phase III+) */
983
984
  sanitizerOptions?: Record<string, any>;
984
985
 
986
+ customSettings?: any;
987
+
985
988
  /** Restricted method settings (Phase IV+) */
986
989
  restrictedMethodSettings?: Array<string | RestrictedMethodConfig>;
987
990
  }
@@ -999,6 +1002,7 @@ export declare class PermissionProcessor {
999
1002
  get hasAttrs(): boolean;
1000
1003
  checkRestrictedProp(key: string): boolean;
1001
1004
  checkRestrictedMethod(methodName: string): boolean;
1005
+ getMethodAppendArgs(methodName: string): any[] | undefined;
1002
1006
  redirectRestrictedProp(target: any, key: string, value: any): boolean;
1003
1007
  checkRestrictedAttributeCall(methodName: string, args: any[]): { blocked: boolean; attrName?: string };
1004
1008
  }
@@ -52,6 +52,11 @@ export interface FaceUpProps {
52
52
  */
53
53
  value: string | File | FormData | null;
54
54
 
55
+ /**
56
+ * The control name used when submitting the form.
57
+ */
58
+ name: string;
59
+
55
60
  /**
56
61
  * Internal state for form restoration (optional).
57
62
  * If provided, passed as the second argument to setFormValue().
@@ -53,6 +53,7 @@ export type Compacts<TProps = any, TActions = TProps, TEvents extends string = s
53
53
  | Partial<{[key in `when_${keyof TProps & string}_changes_dispatch`]: string}>
54
54
  | Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_inc_${keyof TProps & string}_by`]: number}>
55
55
  | Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_set_${keyof TProps & string}_to`]: any}>
56
+ | Partial<{[key in `on_${TEvents}_of_${keyof TProps & string}_assign`]: Record<string, any>}>
56
57
  ;
57
58
 
58
59
  export type Hitches<TProps = any, TActions = TProps> =
@@ -250,7 +250,7 @@ class ElementEnhancementContainer {
250
250
  throw new Error('Instance must be an EventTarget to use whenResolved');
251
251
  }
252
252
  // Lazy load waitForEvent
253
- const { waitForEvent } = await import('./waitForEvent.js');
253
+ const { waitForEvent } = await import('./utils/waitForEvent.js');
254
254
  // Wait for the resolved event (use resolvedKey as event name)
255
255
  // Note: When symbols are supported as event names, this will work with symbol keys too
256
256
  await waitForEvent(spawnedInstance, resolvedKey);
@@ -359,7 +359,7 @@ class ElementEnhancementContainer {
359
359
  }
360
360
 
361
361
  // Lazy load waitForEvent
362
- const { waitForEvent } = await import('./waitForEvent.js');
362
+ const { waitForEvent } = await import('./utils/waitForEvent.js');
363
363
 
364
364
  // Wait for the resolved event (use resolvedKey as event name)
365
365
  // Note: When symbols are supported as event names, this will work with symbol keys too
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.78",
3
+ "version": "0.0.80",
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": {
@@ -159,9 +159,9 @@
159
159
  "default": "./markerUtils.js",
160
160
  "types": "./markerUtils.ts"
161
161
  },
162
- "./waitForSettled.js": {
163
- "default": "./waitForSettled.js",
164
- "types": "./waitForSettled.ts"
162
+ "./utils/waitForSettled.js": {
163
+ "default": "./utils/waitForSettled.js",
164
+ "types": "./utils/waitForSettled.ts"
165
165
  },
166
166
  "./inferredAssignments.js": {
167
167
  "default": "./inferredAssignments.js",
@@ -199,8 +199,9 @@
199
199
  "default": "./evaluatePathWithAsyncMethods.js",
200
200
  "types": "./evaluatePathWithAsyncMethods.ts"
201
201
  },
202
- "./waitForEvent.js": {
203
- "default": "./waitForEvent.js"
202
+ "./utils/waitForEvent.js": {
203
+ "default": "./utils/waitForEvent.js",
204
+ "types": "./utils/waitForEvent.ts"
204
205
  }
205
206
  },
206
207
  "main": "index.js",
@@ -955,7 +955,8 @@ export interface RestrictedPropSetting {
955
955
  */
956
956
  export interface RestrictedMethodConfig {
957
957
  method: string;
958
- addArgs?: string[]; // Phase V: append sanitizer args
958
+ appendArgs?: string[]; // Phase II: append args to each method call
959
+ addArgs?: string[]; // Deprecated alias for appendArgs
959
960
  }
960
961
 
961
962
  /**
@@ -982,6 +983,8 @@ export interface AssignPermissions {
982
983
  /** Sanitizer options (Phase III+) */
983
984
  sanitizerOptions?: Record<string, any>;
984
985
 
986
+ customSettings?: any;
987
+
985
988
  /** Restricted method settings (Phase IV+) */
986
989
  restrictedMethodSettings?: Array<string | RestrictedMethodConfig>;
987
990
  }
@@ -999,6 +1002,7 @@ export declare class PermissionProcessor {
999
1002
  get hasAttrs(): boolean;
1000
1003
  checkRestrictedProp(key: string): boolean;
1001
1004
  checkRestrictedMethod(methodName: string): boolean;
1005
+ getMethodAppendArgs(methodName: string): any[] | undefined;
1002
1006
  redirectRestrictedProp(target: any, key: string, value: any): boolean;
1003
1007
  checkRestrictedAttributeCall(methodName: string, args: any[]): { blocked: boolean; attrName?: string };
1004
1008
  }
File without changes
File without changes
File without changes
File without changes