assign-gingerly 0.0.96 → 0.0.97
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 +77 -1
- package/assignGingerly.js +18 -1
- package/assignGingerly.ts +18 -1
- package/inferencer/types/EnhancementConversionInstructions.md +9 -1
- package/inferencer/types/ImportantEnhancementAddendum.md +46 -0
- package/inferencer/types/assign-gingerly/types.d.ts +36 -3
- package/inferencer/types/be-literate/types.d.ts +1 -1
- package/inferencer/types/be-persistent/types.d.ts +81 -0
- package/inferencer/types/gist-in/types.d.ts +59 -0
- package/object-extension.js +10 -0
- package/object-extension.ts +12 -1
- package/package.json +3 -3
- package/types/assign-gingerly/types.d.ts +2 -0
package/README.md
CHANGED
|
@@ -1594,7 +1594,83 @@ When you access `element.enh.set.enhKey.property`, the proxy:
|
|
|
1594
1594
|
- If a non-matching object already exists at `element.enh[enhKey]`, it's passed as `initVals`
|
|
1595
1595
|
- Stores the spawned instance at `element.enh[enhKey]`
|
|
1596
1596
|
3. **Reuses existing instances**: If the enhancement already exists and is the correct type, it reuses it
|
|
1597
|
-
4. **Falls back to plain objects**: If no registry item is found, creates a plain object at `element.enh[enhKey]`
|
|
1597
|
+
4. **Falls back to plain objects, and waits for registration**: If no registry item is found yet, creates a plain object at `element.enh[enhKey]` and calls `registry.whenDefined(enhKey)` to spawn the real instance automatically once the registry item eventually arrives — see [Setting Properties Before the Registry Item Exists](#setting-properties-before-the-registry-item-exists) below
|
|
1598
|
+
|
|
1599
|
+
</details>
|
|
1600
|
+
|
|
1601
|
+
### Setting Properties Before the Registry Item Exists
|
|
1602
|
+
|
|
1603
|
+
Code doesn't always run in the order you'd like — a module might set
|
|
1604
|
+
properties on an enhancement before whatever registers that enhancement's
|
|
1605
|
+
config has had a chance to run. `enh.set` handles this automatically, the
|
|
1606
|
+
same way `element.enh.set.enhKey.property = value` does when the registry
|
|
1607
|
+
item is already there:
|
|
1608
|
+
|
|
1609
|
+
```TypeScript
|
|
1610
|
+
import 'assign-gingerly/object-extension.js';
|
|
1611
|
+
|
|
1612
|
+
const myElement = document.createElement('div');
|
|
1613
|
+
|
|
1614
|
+
// Nothing has called `registry.push(...)` for 'myEnh' yet.
|
|
1615
|
+
myElement.enh.set.myEnh.myProp = 'hello'; // plain placeholder, collected as pending initVals
|
|
1616
|
+
myElement.enh.myEnh.anotherProp = 'world'; // direct property access works too, same placeholder
|
|
1617
|
+
|
|
1618
|
+
console.log(myElement.enh.myEnh instanceof MyEnhancement); // false -- still a plain object
|
|
1619
|
+
|
|
1620
|
+
// ...later, whenever the registry item shows up...
|
|
1621
|
+
const registry = myElement.customElementRegistry.enhancementRegistry;
|
|
1622
|
+
registry.push({ spawn: MyEnhancement, enhKey: 'myEnh' });
|
|
1623
|
+
|
|
1624
|
+
// The spawn happens automatically once `whenDefined` resolves -- no need to
|
|
1625
|
+
// touch `myElement.enh.myEnh` again yourself.
|
|
1626
|
+
await registry.whenDefined('myEnh');
|
|
1627
|
+
console.log(myElement.enh.myEnh instanceof MyEnhancement); // true
|
|
1628
|
+
console.log(myElement.enh.myEnh.myProp); // 'hello' -- both properties
|
|
1629
|
+
console.log(myElement.enh.myEnh.anotherProp); // 'world' -- survived as initVals
|
|
1630
|
+
```
|
|
1631
|
+
|
|
1632
|
+
<details>
|
|
1633
|
+
<summary>How it works</summary>
|
|
1634
|
+
|
|
1635
|
+
1. The first `enh.set.enhKey.property = value` assignment for an
|
|
1636
|
+
unregistered `enhKey` creates a plain object at `element.enh[enhKey]` (as
|
|
1637
|
+
always) and, in addition, calls
|
|
1638
|
+
`registry.whenDefined(enhKey).then(() => element.enh.get(enhKey))`.
|
|
1639
|
+
2. Because that call only happens inside the `if (self[prop] === undefined)`
|
|
1640
|
+
branch, it fires exactly once per element/`enhKey` pair — further property
|
|
1641
|
+
assignments before registration just add to the same placeholder object,
|
|
1642
|
+
they don't queue additional spawn attempts.
|
|
1643
|
+
3. `registry.whenDefined(enhKey)` resolves once a matching `enhKey` is
|
|
1644
|
+
`push`ed into the registry (see below).
|
|
1645
|
+
4. Once it resolves, `enh.get(enhKey)` runs — the same method used everywhere
|
|
1646
|
+
else in this library. It finds the plain placeholder already sitting at
|
|
1647
|
+
`element.enh[enhKey]`, treats it as `initVals` (this is the same
|
|
1648
|
+
"non-matching object passed as `initVals`" behavior described in step 2 of
|
|
1649
|
+
[How It Works](#basic-usage) above — nothing new here), constructs the real
|
|
1650
|
+
instance, and overwrites the placeholder with it.
|
|
1651
|
+
|
|
1652
|
+
**`EnhancementRegistry.whenDefined(enhKey)`** — mirrors
|
|
1653
|
+
[`customElements.whenDefined(name)`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined):
|
|
1654
|
+
|
|
1655
|
+
- If `enhKey` is already registered, resolves once any of its pending
|
|
1656
|
+
[Custom Element Features](#custom-element-features) setup has settled (see
|
|
1657
|
+
`features` on `EnhancementConfig`).
|
|
1658
|
+
- If `enhKey` isn't registered yet, first waits for a future `registry.push(...)`
|
|
1659
|
+
call whose config (a single item, or an array) includes a matching `enhKey`,
|
|
1660
|
+
then does the same features-setup wait.
|
|
1661
|
+
- **If `enhKey` is never registered, this promise never resolves** — same
|
|
1662
|
+
tradeoff `customElements.whenDefined` makes. Don't `await` it unconditionally
|
|
1663
|
+
in code paths where registration isn't guaranteed to eventually happen.
|
|
1664
|
+
|
|
1665
|
+
**Limitations while waiting:**
|
|
1666
|
+
- The placeholder is a plain object — no reactivity, no defaults, no
|
|
1667
|
+
validation. Reading a property other than the ones you've explicitly set
|
|
1668
|
+
(e.g. checking `resolved`) just returns `undefined`, since nothing has
|
|
1669
|
+
spawned yet.
|
|
1670
|
+
- `mountCtx` still isn't available through this path (same restriction as
|
|
1671
|
+
`enh.set` generally — see the note in
|
|
1672
|
+
[Passing Custom Context](#constructor-signature) above); the eventual
|
|
1673
|
+
`enh.get(enhKey)` call is made with no `mountCtx`.
|
|
1598
1674
|
|
|
1599
1675
|
</details>
|
|
1600
1676
|
|
package/assignGingerly.js
CHANGED
|
@@ -63,10 +63,27 @@ export class EnhancementRegistry extends EventTarget {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Resolves once `enhKey` is defined -- i.e. registered via `push` -- and
|
|
67
|
+
* ready (any pending `features` setup for it has settled). Mirrors
|
|
68
|
+
* `customElements.whenDefined(name)`: if the key isn't registered yet, waits
|
|
69
|
+
* for a future `push` that registers it; if it's never registered, this
|
|
70
|
+
* never resolves (same tradeoff as the platform method).
|
|
67
71
|
* @param enhKey - Enhancement key to wait for
|
|
68
72
|
*/
|
|
69
73
|
async whenDefined(enhKey) {
|
|
74
|
+
if (!this.findByEnhKey(enhKey)) {
|
|
75
|
+
await new Promise((resolve) => {
|
|
76
|
+
const handler = (event) => {
|
|
77
|
+
const config = event.config;
|
|
78
|
+
const items = Array.isArray(config) ? config : [config];
|
|
79
|
+
if (items.some(item => item.enhKey === enhKey)) {
|
|
80
|
+
this.removeEventListener(EnhancementRegisteredEvent.eventName, handler);
|
|
81
|
+
resolve();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
this.addEventListener(EnhancementRegisteredEvent.eventName, handler);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
70
87
|
const pending = this.#pendingSetups.get(enhKey);
|
|
71
88
|
if (pending && pending.length > 0) {
|
|
72
89
|
await Promise.all(pending);
|
package/assignGingerly.ts
CHANGED
|
@@ -106,10 +106,27 @@ export class EnhancementRegistry extends EventTarget {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
*
|
|
109
|
+
* Resolves once `enhKey` is defined -- i.e. registered via `push` -- and
|
|
110
|
+
* ready (any pending `features` setup for it has settled). Mirrors
|
|
111
|
+
* `customElements.whenDefined(name)`: if the key isn't registered yet, waits
|
|
112
|
+
* for a future `push` that registers it; if it's never registered, this
|
|
113
|
+
* never resolves (same tradeoff as the platform method).
|
|
110
114
|
* @param enhKey - Enhancement key to wait for
|
|
111
115
|
*/
|
|
112
116
|
async whenDefined(enhKey: EnhKey): Promise<void> {
|
|
117
|
+
if (!this.findByEnhKey(enhKey)) {
|
|
118
|
+
await new Promise<void>((resolve) => {
|
|
119
|
+
const handler = (event: Event) => {
|
|
120
|
+
const config = (event as EnhancementRegisteredEvent).config;
|
|
121
|
+
const items = Array.isArray(config) ? config : [config];
|
|
122
|
+
if (items.some(item => item.enhKey === enhKey)) {
|
|
123
|
+
this.removeEventListener(EnhancementRegisteredEvent.eventName, handler);
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
this.addEventListener(EnhancementRegisteredEvent.eventName, handler);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
113
130
|
const pending = this.#pendingSetups.get(enhKey);
|
|
114
131
|
if (pending && pending.length > 0) {
|
|
115
132
|
await Promise.all(pending);
|
|
@@ -107,7 +107,7 @@ Update the package.json to use the modern architecture's dependencies and build
|
|
|
107
107
|
"nested-regex-groups": "*"
|
|
108
108
|
}
|
|
109
109
|
```
|
|
110
|
-
- Drop the legacy dependencies (`be-enhanced`, `trans-render`, etc.).
|
|
110
|
+
- Drop the legacy dependencies (`be-enhanced`, `trans-render`, etc.). There is a modern replacement for most legacy utilities — e.g. Uniform Storage Path writes move from `trans-render/XV/set.js` to [`fifteenth`](https://github.com/bahrus/fifteenth) (`fifteenth/set.js`, see Step 9). Keep a legacy dependency **only** if a converted action still imports from it and no replacement exists — and note any such carry-over in your conversion notes.
|
|
111
111
|
- `mount-observer` is listed as a direct dependency so it lands at the root `node_modules/` level, accessible via the import map. `nested-regex-groups` is optional but recommended if your enhancement needs custom attribute parsing (Step 7a).
|
|
112
112
|
- The exact version strings do not matter here — the next step replaces them all with the latest published versions.
|
|
113
113
|
|
|
@@ -969,6 +969,14 @@ export interface Actions{
|
|
|
969
969
|
7. **Update utility imports**:
|
|
970
970
|
- Replace `trans-render/lib/findAdjacentElement.js` with `be-hive/findAdjacentElement.js`
|
|
971
971
|
- The be-hive package provides common utilities that were previously in trans-render
|
|
972
|
+
- **Uniform Storage Path (USL) writes** — if a legacy action wrote values to `indexedDB://…`, `localStorage://…`, etc. via `trans-render/XV/set.js`, use [`fifteenth`](https://github.com/bahrus/fifteenth) instead:
|
|
973
|
+
```javascript
|
|
974
|
+
const { set } = await import('fifteenth/set.js');
|
|
975
|
+
await set('indexedDB://myDB/myStore/myKey', value); // same (usl, val, ctx?) signature as XV/set
|
|
976
|
+
```
|
|
977
|
+
- Add `"fifteenth"` to `dependencies` and a `"fifteenth/": "/node_modules/fifteenth/"` entry to the import map.
|
|
978
|
+
- `fifteenth` imports `parseProtocolRef` from `assign-gingerly@0.0.96`+. `roundabout-lib` / `mount-observer` may still pin an older `assign-gingerly` exactly, and the flat import map can only serve one copy — so also add `"assign-gingerly": "<the version fifteenth needs>"` to `dependencies` to hoist a single root copy. Verify the be-hive/mount-observer/roundabout read path still works against that version.
|
|
979
|
+
- **Behavior change to note in your conversion notes:** `fifteenth`'s `set` broadcasts `window.postMessage([usp])` (or `[usp, usl]` when the USL has a `?.` accessor chain) — a plain `Array` of path strings, *not* the `Set` (protocol + store-root + full-path) that `trans-render/XV` posted. Update any `message` listeners accordingly.
|
|
972
980
|
|
|
973
981
|
**CRITICAL - Avoid Compact/Action Conflicts:**
|
|
974
982
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Clarify Programmatic Way to Add Enhancements
|
|
2
|
+
|
|
3
|
+
When we define an enhancement, it is easiest to demonstrate what it does and how it works by providing simple HTML examples with the attributes used to trigger the enhancement.
|
|
4
|
+
|
|
5
|
+
These examples are skewed towards an important use case -- progressive enhancement of server rendered content.
|
|
6
|
+
|
|
7
|
+
However, currently, only a small fraction of web development centers around that paradigm. Rather, it tends to revolve around client-side rendering, using a framework.
|
|
8
|
+
|
|
9
|
+
In such a context, putting so much emphasis on the attribute way of hooking things up is problematic.
|
|
10
|
+
|
|
11
|
+
1. Such frameworks are clumsy when it comes to setting attributes.
|
|
12
|
+
2. The amount of object stringifying and parsing is inefficient.
|
|
13
|
+
|
|
14
|
+
So we should make an effort to showcase how to integrate with these enhancements efficiently and ergonomically in such settings. Basically programmatically without the use of attributes.
|
|
15
|
+
|
|
16
|
+
I think we should showcase two ways to do so.
|
|
17
|
+
|
|
18
|
+
So for example, currently the README.md for be-persistent shows the example:
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<input be-persistent="of value@input via sessionStorage://{autoGenId}.">
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
We should also show:
|
|
25
|
+
|
|
26
|
+
## Imperative Enhancement Attachment:
|
|
27
|
+
|
|
28
|
+
```JS
|
|
29
|
+
import {emc} from 'be-persistent/emc.json' with {type: 'json'};
|
|
30
|
+
import {BePersistent} from 'be-persistent/be-persistent.js';
|
|
31
|
+
emc.spawn = BePersistent;
|
|
32
|
+
const persistenceEnhancement = oInput.enh.get(emc);
|
|
33
|
+
persistenceEnhancement.store =
|
|
34
|
+
{
|
|
35
|
+
localProp: 'value', //default
|
|
36
|
+
localEvent: 'input', //default
|
|
37
|
+
usl: 'sessionStorage://{autoGenId}'
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Declarative Enhancement Attachment:
|
|
42
|
+
|
|
43
|
+
```JS
|
|
44
|
+
import 'be-persistent/.js';
|
|
45
|
+
|
|
46
|
+
```
|
|
@@ -72,6 +72,8 @@ export interface EnhancementConfig<T = any, Obj = Element> extends EnhancementCo
|
|
|
72
72
|
* Calls assignFeatures(spawn, features) automatically on registration.
|
|
73
73
|
*/
|
|
74
74
|
features?: FeatureConfigsMap;
|
|
75
|
+
|
|
76
|
+
customData?: any;
|
|
75
77
|
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -406,7 +408,7 @@ export interface AssignFromOptions {
|
|
|
406
408
|
from: any;
|
|
407
409
|
|
|
408
410
|
/** Protocol handlers (sync or async) */
|
|
409
|
-
protocols?:
|
|
411
|
+
protocols?: ProtocolHandlers;
|
|
410
412
|
|
|
411
413
|
/** Method names to call during path evaluation (append `|` to a path segment for a zero-argument call) */
|
|
412
414
|
withMethods?: string[] | Set<string>;
|
|
@@ -480,6 +482,37 @@ export interface AssignFromOptions {
|
|
|
480
482
|
[key: string]: any;
|
|
481
483
|
}
|
|
482
484
|
|
|
485
|
+
/**
|
|
486
|
+
* A protocol handler resolves the key portion of a protocol-prefixed value
|
|
487
|
+
* (the text between `://` and the first `?.`) to a value. May be sync or async.
|
|
488
|
+
*/
|
|
489
|
+
export type ProtocolHandler = (key: string) => any | Promise<any>;
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* A synchronous-only protocol handler, as accepted by getValues / getValue /
|
|
493
|
+
* assignFrom. For handlers that may return a Promise, use ProtocolHandler.
|
|
494
|
+
*/
|
|
495
|
+
export type SyncProtocolHandler = (key: string) => any;
|
|
496
|
+
|
|
497
|
+
/** Map of protocol name (the text before `://`) to a handler (sync or async). */
|
|
498
|
+
export type ProtocolHandlers = Record<string, ProtocolHandler>;
|
|
499
|
+
|
|
500
|
+
/** Map of protocol name to a synchronous handler. */
|
|
501
|
+
export type SyncProtocolHandlers = Record<string, SyncProtocolHandler>;
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* The outer grammar of a protocol-prefixed value string,
|
|
505
|
+
* `‹protocol›://‹key›?.‹path›`, as produced by `parseProtocolRef`.
|
|
506
|
+
*/
|
|
507
|
+
export interface ParsedProtocolRef {
|
|
508
|
+
/** Text before `://`; `''` when the value contains no `://`. */
|
|
509
|
+
protocol: string;
|
|
510
|
+
/** Text between `://` and the first `?.` (or the end of the string). */
|
|
511
|
+
key: string;
|
|
512
|
+
/** Text from the first `?.` onward (always starts with `?.`), or `null`. */
|
|
513
|
+
path: string | null;
|
|
514
|
+
}
|
|
515
|
+
|
|
483
516
|
/**
|
|
484
517
|
* Options for synchronous value resolution (getValues / getValue).
|
|
485
518
|
* Extends IAssignGingerlyOptions with synchronous protocol handlers.
|
|
@@ -507,7 +540,7 @@ export interface GetValuesOptions extends IAssignGingerlyOptions {
|
|
|
507
540
|
* localStorage: (key) => JSON.parse(localStorage.getItem(key) || 'null')
|
|
508
541
|
* }
|
|
509
542
|
*/
|
|
510
|
-
protocols?:
|
|
543
|
+
protocols?: SyncProtocolHandlers;
|
|
511
544
|
}
|
|
512
545
|
|
|
513
546
|
/**
|
|
@@ -530,7 +563,7 @@ export interface ResolveValuesOptions extends IAssignGingerlyOptions {
|
|
|
530
563
|
* Protocol handlers for resolving protocol-prefixed values (e.g., 'globalThis://key').
|
|
531
564
|
* Each handler receives the key portion and returns the resolved value (sync or async).
|
|
532
565
|
*/
|
|
533
|
-
protocols?:
|
|
566
|
+
protocols?: ProtocolHandlers;
|
|
534
567
|
}
|
|
535
568
|
|
|
536
569
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Uniform Storage Path string (e.g. "indexedDB://myDB/myFiles/{file.name}").
|
|
3
|
-
*
|
|
3
|
+
* Consumed by `fifteenth`'s `set`. Kept as a local alias so this file stays standalone.
|
|
4
4
|
*/
|
|
5
5
|
export type USL = string;
|
|
6
6
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ElementEnhancementGateway, SpawnContext } from "../assign-gingerly/types";
|
|
2
|
+
import { StatementsResult } from "../nested-regex-groups/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The programmatic shape of `store`: a bare USL string (shorthand for
|
|
6
|
+
* `{usl}`, i.e. `localProp`/`localEvent` take their defaults), a single
|
|
7
|
+
* {@link PersistenceRule}, or an array of them (one binder per entry).
|
|
8
|
+
* This is the property JS callers set directly — e.g.
|
|
9
|
+
* `persistenceEnhancement.store = 'sessionStorage://{autoGenId}'` — without
|
|
10
|
+
* ever going through attribute parsing.
|
|
11
|
+
*/
|
|
12
|
+
export type PersistenceRuleConfig = string | PersistenceRule | PersistenceRule[];
|
|
13
|
+
|
|
14
|
+
export interface EndUserProps {
|
|
15
|
+
/**
|
|
16
|
+
* What to persist and where. Accepts a USL string, a single
|
|
17
|
+
* {@link PersistenceRule}, or an array of rules — see
|
|
18
|
+
* {@link PersistenceRuleConfig}. This is the property `hydrate` reads and
|
|
19
|
+
* the one programmatic (attribute-free) callers should assign.
|
|
20
|
+
*/
|
|
21
|
+
store?: PersistenceRuleConfig;
|
|
22
|
+
/**
|
|
23
|
+
* Opt-in flag set by the boolean `be-persistent-nudge` / `💾-nudge`
|
|
24
|
+
* attribute. When present, `hydrate` waits for every rule to finish its
|
|
25
|
+
* initial storage reconciliation and then calls `assign-gingerly`'s
|
|
26
|
+
* `nudge` on the enhanced element — decrementing its `disabled` counter so
|
|
27
|
+
* an element that was disabled only to block edits before its persisted
|
|
28
|
+
* value loaded becomes interactive.
|
|
29
|
+
*/
|
|
30
|
+
nudge?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AllProps extends EndUserProps {
|
|
34
|
+
enhancedElement: Element & ElementEnhancementGateway;
|
|
35
|
+
resolved?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Flipped to `true` at the end of `init`, once `roundabout` has finished
|
|
38
|
+
* assigning every attribute-derived prop. `hydrate` is gated on it so it
|
|
39
|
+
* never runs against a half-populated view model.
|
|
40
|
+
*/
|
|
41
|
+
initialized?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Raw parse result of the base attribute (`be-persistent` / `💾`), via the
|
|
44
|
+
* `parse-grouped-capture-statements` built-in parser. Exists **only** to be
|
|
45
|
+
* transferred into `store` by `onPersistenceRulesChange` — nothing else
|
|
46
|
+
* reads it. Programmatic callers should assign `store` directly instead of
|
|
47
|
+
* this property.
|
|
48
|
+
*/
|
|
49
|
+
persistenceRules?: StatementsResult<PersistenceRule>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type AP = AllProps;
|
|
53
|
+
|
|
54
|
+
export type PAP = Partial<AP>;
|
|
55
|
+
|
|
56
|
+
export type ProPAP = Promise<PAP>;
|
|
57
|
+
|
|
58
|
+
export interface Actions {
|
|
59
|
+
init(self: AP, enhancedElement: Element & ElementEnhancementGateway, ctx: SpawnContext, initVals: PAP): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Compact-invoked (`when_persistenceRules_changes_call_onPersistenceRulesChange`),
|
|
62
|
+
* not an action — normalizes `persistenceRules.statements` into `store`.
|
|
63
|
+
*/
|
|
64
|
+
onPersistenceRulesChange(self: AP): PAP;
|
|
65
|
+
hydrate(self: AP): ProPAP;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface PersistenceRule {
|
|
69
|
+
/** Element property to read/write (e.g. `value`, `checked`, `innerHTML`). Defaults to `value`. */
|
|
70
|
+
localProp?: string;
|
|
71
|
+
/** DOM event that triggers a save. Defaults to `input`. */
|
|
72
|
+
localEvent?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Uniform Storage Locator describing where the value lives, e.g.
|
|
75
|
+
* `sessionStorage://{autoGenId}`, `indexedDB://myDB/myStore/{autoGenId}`,
|
|
76
|
+
* `cookie://{autoGenId}`, `locationHash://{autoGenId}`.
|
|
77
|
+
* `{autoGenId}` is expanded at runtime to a location-independent DOM path.
|
|
78
|
+
* Defaults to `sessionStorage://{autoGenId}`.
|
|
79
|
+
*/
|
|
80
|
+
usl?: string;
|
|
81
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ElementEnhancementGateway, SpawnContext } from "../assign-gingerly/types";
|
|
2
|
+
|
|
3
|
+
export interface EndUserProps {
|
|
4
|
+
/**
|
|
5
|
+
* Where to fetch the replacement content from — either a literal URL
|
|
6
|
+
* (typically a `gist.githubusercontent.com/.../raw/.../<file>` URL) or a
|
|
7
|
+
* `fifteenth` `gist://` USL (`gist://<owner>/<id>/raw[/<sha>]/<file>`,
|
|
8
|
+
* or an alias/`=<id>` form once `configureGist()` has registered the
|
|
9
|
+
* `gist` protocol).
|
|
10
|
+
*/
|
|
11
|
+
url: string;
|
|
12
|
+
/** The `<?marker name="…">` this template's content replaces. */
|
|
13
|
+
markerName: string;
|
|
14
|
+
/**
|
|
15
|
+
* CSS selector naming the parent(s) of the marker(s), so the search
|
|
16
|
+
* doesn't have to `TreeWalker` the whole subtree. Optional.
|
|
17
|
+
*/
|
|
18
|
+
forHint: string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Show a link to edit the gist on GitHub right after the template.
|
|
21
|
+
* Also triggered page-wide by `?gist-in-show-edit-link=true` in the URL.
|
|
22
|
+
*/
|
|
23
|
+
showEditLink: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Custom Sanitizer configuration (JSON object, e.g.
|
|
26
|
+
* `{"elements":["option","optgroup"]}`), same shape as pipe-in's
|
|
27
|
+
* `[base]-sanitizer`. The platform's *default* sanitizer strips elements
|
|
28
|
+
* like `<option>` entirely — this is the escape hatch for patching them.
|
|
29
|
+
* Gated the same way pipe-in gates it: only honored when `url` is a
|
|
30
|
+
* same-origin path, an import-map-resolved bare specifier, or a
|
|
31
|
+
* `gist://` USL (whose real destination is always the fixed
|
|
32
|
+
* `gist.githubusercontent.com` host, not attacker-steerable).
|
|
33
|
+
*/
|
|
34
|
+
sanitizer: object | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* `'setHTML'` (default, sanitized) or `'setHTMLUnsafe'` (no sanitizing at
|
|
37
|
+
* all) — named after, and selecting between, the two real underlying
|
|
38
|
+
* methods, the same way pipe-in's `[base]-method` selects one of its own
|
|
39
|
+
* `streamHTML`/`streamHTMLUnsafe`/etc. `'setHTMLUnsafe'` is gated the same
|
|
40
|
+
* as a custom `sanitizer` (see above). Never runs embedded `<script>`s
|
|
41
|
+
* either way — `setHTML`/`setHTMLUnsafe` parse, they don't execute
|
|
42
|
+
* (verified; same as plain `innerHTML`).
|
|
43
|
+
*/
|
|
44
|
+
method: 'setHTML' | 'setHTMLUnsafe';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface AllProps extends EndUserProps {
|
|
48
|
+
enhancedElement: Element & ElementEnhancementGateway;
|
|
49
|
+
resolved: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type AP = AllProps;
|
|
53
|
+
export type PAP = Partial<AP>;
|
|
54
|
+
export type ProPAP = Promise<PAP>;
|
|
55
|
+
|
|
56
|
+
export interface Actions {
|
|
57
|
+
init(self: AP, enhancedElement: Element, ctx: SpawnContext, initVals: PAP): Promise<void>;
|
|
58
|
+
hydrate(self: AP): ProPAP;
|
|
59
|
+
}
|
package/object-extension.js
CHANGED
|
@@ -317,6 +317,16 @@ class ElementEnhancementContainer {
|
|
|
317
317
|
// No registry item found - create plain object if needed
|
|
318
318
|
if (self[prop] === undefined) {
|
|
319
319
|
self[prop] = {};
|
|
320
|
+
// Declarative-out-of-sequence: `prop`'s enhancement config hasn't
|
|
321
|
+
// been registered yet. Once it is, hand the plain object we just
|
|
322
|
+
// created off to `get()` as pending initVals and let it spawn
|
|
323
|
+
// normally -- `get()` already treats a non-instance value sitting
|
|
324
|
+
// in this slot as initVals to merge in (see the `existingInitVals`
|
|
325
|
+
// check above). If `prop` is never registered, this never
|
|
326
|
+
// resolves (same tradeoff as `customElements.whenDefined`).
|
|
327
|
+
const laterRegistry = registry
|
|
328
|
+
?? (typeof customElements !== 'undefined' ? customElements.enhancementRegistry : undefined);
|
|
329
|
+
laterRegistry?.whenDefined(prop).then(() => self.get(prop));
|
|
320
330
|
}
|
|
321
331
|
return self[prop];
|
|
322
332
|
}
|
package/object-extension.ts
CHANGED
|
@@ -443,8 +443,19 @@ class ElementEnhancementContainer {
|
|
|
443
443
|
// No registry item found - create plain object if needed
|
|
444
444
|
if (self[prop] === undefined) {
|
|
445
445
|
self[prop] = {};
|
|
446
|
+
|
|
447
|
+
// Declarative-out-of-sequence: `prop`'s enhancement config hasn't
|
|
448
|
+
// been registered yet. Once it is, hand the plain object we just
|
|
449
|
+
// created off to `get()` as pending initVals and let it spawn
|
|
450
|
+
// normally -- `get()` already treats a non-instance value sitting
|
|
451
|
+
// in this slot as initVals to merge in (see the `existingInitVals`
|
|
452
|
+
// check above). If `prop` is never registered, this never
|
|
453
|
+
// resolves (same tradeoff as `customElements.whenDefined`).
|
|
454
|
+
const laterRegistry = registry
|
|
455
|
+
?? (typeof customElements !== 'undefined' ? (customElements as any).enhancementRegistry : undefined);
|
|
456
|
+
laterRegistry?.whenDefined(prop).then(() => self.get(prop));
|
|
446
457
|
}
|
|
447
|
-
|
|
458
|
+
|
|
448
459
|
return self[prop];
|
|
449
460
|
}
|
|
450
461
|
}) as any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.97",
|
|
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": {
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
"chrome": "npx playwright cr http://localhost:8000"
|
|
229
229
|
},
|
|
230
230
|
"devDependencies": {
|
|
231
|
-
"@playwright/test": "1.
|
|
232
|
-
"spa-ssi": "0.0.
|
|
231
|
+
"@playwright/test": "1.63.0",
|
|
232
|
+
"spa-ssi": "0.0.28"
|
|
233
233
|
}
|
|
234
234
|
}
|