ember-primitives 0.37.0 → 0.39.0
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/declarations/components/portal-targets.d.ts +15 -1
- package/declarations/components/portal.d.ts +9 -3
- package/declarations/narrowing.d.ts +2 -0
- package/declarations/store.d.ts +39 -0
- package/declarations/type-utils.d.ts +3 -0
- package/declarations/utils.d.ts +1 -0
- package/dist/components/portal-targets.js +58 -4
- package/dist/components/portal-targets.js.map +1 -1
- package/dist/components/portal.js +35 -6
- package/dist/components/portal.js.map +1 -1
- package/dist/narrowing.js +9 -0
- package/dist/narrowing.js.map +1 -0
- package/dist/proper-links.js.map +1 -1
- package/dist/store.js +63 -0
- package/dist/store.js.map +1 -0
- package/dist/type-utils.js +2 -0
- package/dist/type-utils.js.map +1 -0
- package/dist/utils.js +5 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,9 +4,23 @@ export declare const TARGETS: Readonly<{
|
|
|
4
4
|
tooltip: "ember-primitives__portal-targets__tooltip";
|
|
5
5
|
modal: "ember-primitives__portal-targets__modal";
|
|
6
6
|
}>;
|
|
7
|
-
export declare function findNearestTarget(origin: Element, name: string): Element;
|
|
7
|
+
export declare function findNearestTarget(origin: Element, name: string): Element | undefined;
|
|
8
8
|
export interface Signature {
|
|
9
9
|
Element: null;
|
|
10
10
|
}
|
|
11
11
|
export declare const PortalTargets: TOC<Signature>;
|
|
12
|
+
/**
|
|
13
|
+
* For manually registering a PortalTarget for use with Portal
|
|
14
|
+
*/
|
|
15
|
+
export declare const PortalTarget: TOC<{
|
|
16
|
+
Element: HTMLDivElement;
|
|
17
|
+
Args: {
|
|
18
|
+
/**
|
|
19
|
+
* The name of the PortalTarget
|
|
20
|
+
*
|
|
21
|
+
* This exact string may be passed to `Portal`'s `@to` argument.
|
|
22
|
+
*/
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
12
26
|
export default PortalTargets;
|
|
@@ -8,15 +8,21 @@ export interface Signature {
|
|
|
8
8
|
* This is the value of the `data-portal-name` attribute
|
|
9
9
|
* of the element you wish to render in to.
|
|
10
10
|
*
|
|
11
|
-
* This can also be an Element which pairs nicely with query-utilities such as
|
|
11
|
+
* This can also be an Element which pairs nicely with query-utilities such as the platform-native `querySelector`
|
|
12
12
|
*/
|
|
13
|
-
to
|
|
13
|
+
to?: (Targets | (string & {})) | Element;
|
|
14
14
|
/**
|
|
15
15
|
* Set to true to append to the portal instead of replace
|
|
16
16
|
*
|
|
17
17
|
* Default: false
|
|
18
18
|
*/
|
|
19
19
|
append?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* For ember-wormhole style behavior, this argument may be an id,
|
|
22
|
+
* or a selector.
|
|
23
|
+
* This can also be an element, in which case the behavior is identical to `@to`
|
|
24
|
+
*/
|
|
25
|
+
wormhole?: string | Element;
|
|
20
26
|
};
|
|
21
27
|
Blocks: {
|
|
22
28
|
/**
|
|
@@ -42,6 +48,6 @@ export interface Signature {
|
|
|
42
48
|
*
|
|
43
49
|
* ```
|
|
44
50
|
*/
|
|
45
|
-
export declare function wormhole(query: string | null | undefined | Element): Element;
|
|
51
|
+
export declare function wormhole(query: string | null | undefined | Element): Element | null;
|
|
46
52
|
export declare const Portal: TOC<Signature>;
|
|
47
53
|
export default Portal;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Newable } from './type-utils.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a singleton for the given context and links the lifetime of the created class to the passed context
|
|
4
|
+
*
|
|
5
|
+
* Note that this function is _not_ lazy. Calling `createStore` will create an instance of the passed class.
|
|
6
|
+
* When combined with a getter though, creation becomes lazy.
|
|
7
|
+
*
|
|
8
|
+
* In this example, `MyState` is created once per instance of the component.
|
|
9
|
+
* repeat accesses to `this.foo` return a stable reference _as if_ `@cached` were used.
|
|
10
|
+
* ```js
|
|
11
|
+
* class MyState {}
|
|
12
|
+
*
|
|
13
|
+
* class Demo extends Component {
|
|
14
|
+
* // this is a stable reference
|
|
15
|
+
* get foo() {
|
|
16
|
+
* return createStore(this, MyState);
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* // or
|
|
20
|
+
* bar = createStore(this, MyState);
|
|
21
|
+
*
|
|
22
|
+
* // or
|
|
23
|
+
* three = createStore(this, () => new MyState(1, 2));
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* If arguments need to be configured during construction, the second argument may also be a function
|
|
28
|
+
* ```js
|
|
29
|
+
* class MyState {}
|
|
30
|
+
*
|
|
31
|
+
* class Demo extends Component {
|
|
32
|
+
* // this is a stable reference
|
|
33
|
+
* get foo() {
|
|
34
|
+
* return createStore(this, MyState);
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function createStore<Instance extends object>(context: object, theClass: Newable<Instance> | (() => Instance)): Instance;
|
package/declarations/utils.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { assert } from '@ember/debug';
|
|
2
2
|
import { macroCondition, isDevelopingApp } from '@embroider/macros';
|
|
3
|
+
import { modifier } from 'ember-modifier';
|
|
4
|
+
import { TrackedMap, TrackedSet } from 'tracked-built-ins';
|
|
3
5
|
import { precompileTemplate } from '@ember/template-compilation';
|
|
4
6
|
import { setComponentTemplate } from '@ember/component';
|
|
5
7
|
import templateOnly from '@ember/component/template-only';
|
|
6
8
|
|
|
9
|
+
const cache = new TrackedMap();
|
|
7
10
|
const TARGETS = Object.freeze({
|
|
8
11
|
popover: "ember-primitives__portal-targets__popover",
|
|
9
12
|
tooltip: "ember-primitives__portal-targets__tooltip",
|
|
@@ -14,8 +17,31 @@ function findNearestTarget(origin, name) {
|
|
|
14
17
|
assert(`second argument to \`findNearestTarget\` must be a string`, typeof name === `string`);
|
|
15
18
|
let element = null;
|
|
16
19
|
let parent = origin.parentNode;
|
|
20
|
+
const manuallyRegisteredSet = cache.get(name);
|
|
21
|
+
const manuallyRegistered = manuallyRegisteredSet?.size ? [...manuallyRegisteredSet] : null;
|
|
22
|
+
/**
|
|
23
|
+
* For use with <PortalTarget @name="hi" />
|
|
24
|
+
*/
|
|
25
|
+
function findRegistered(host) {
|
|
26
|
+
return manuallyRegistered?.find(element => {
|
|
27
|
+
if (host.contains(element)) {
|
|
28
|
+
return element;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const selector = Object.values(TARGETS).includes(name) ? `[data-portal-name=${name}]` : name;
|
|
33
|
+
/**
|
|
34
|
+
* Default portals / non-registered -- here we match a query selector instead of an element
|
|
35
|
+
*/
|
|
36
|
+
function findDefault(host) {
|
|
37
|
+
return host.querySelector(selector);
|
|
38
|
+
}
|
|
39
|
+
const finder = manuallyRegistered ? findRegistered : findDefault;
|
|
40
|
+
/**
|
|
41
|
+
* Crawl up the ancestry looking for our portal target
|
|
42
|
+
*/
|
|
17
43
|
while (!element && parent) {
|
|
18
|
-
element = parent
|
|
44
|
+
element = finder(parent);
|
|
19
45
|
if (element) break;
|
|
20
46
|
parent = parent.parentNode;
|
|
21
47
|
}
|
|
@@ -23,15 +49,43 @@ function findNearestTarget(origin, name) {
|
|
|
23
49
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
24
50
|
window.prime0 = origin;
|
|
25
51
|
}
|
|
26
|
-
|
|
27
|
-
|
|
52
|
+
if (name.startsWith("ember-primitives")) {
|
|
53
|
+
assert(`Could not find element by the given name: \`${name}\`.` + ` The known names are ` + `${Object.values(TARGETS).join(", ")} ` + `-- but any name will work as long as it is set to the \`data-portal-name\` attribute ` + `(or if the name has been specifically registered via the <PortalTarget /> component). ` + `Double check that the element you're wanting to portal to is rendered. ` + `The element passed to \`findNearestTarget\` is stored on \`window.prime0\` ` + `You can debug in your browser's console via ` + `\`document.querySelector('[data-portal-name="${name}"]')\``, element);
|
|
54
|
+
}
|
|
55
|
+
return element ?? undefined;
|
|
28
56
|
}
|
|
57
|
+
const register = modifier((element, [name]) => {
|
|
58
|
+
assert(`@name is required when using <PortalTarget>`, name);
|
|
59
|
+
void (async () => {
|
|
60
|
+
// Bad TypeScript lint.
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
62
|
+
await 0;
|
|
63
|
+
let existing = cache.get(name);
|
|
64
|
+
if (!existing) {
|
|
65
|
+
existing = new TrackedSet();
|
|
66
|
+
cache.set(name, existing);
|
|
67
|
+
}
|
|
68
|
+
existing.add(element);
|
|
69
|
+
})();
|
|
70
|
+
return () => {
|
|
71
|
+
cache.delete(name);
|
|
72
|
+
};
|
|
73
|
+
});
|
|
29
74
|
const PortalTargets = setComponentTemplate(precompileTemplate("\n <div data-portal-name={{TARGETS.popover}}></div>\n <div data-portal-name={{TARGETS.tooltip}}></div>\n <div data-portal-name={{TARGETS.modal}}></div>\n", {
|
|
30
75
|
strictMode: true,
|
|
31
76
|
scope: () => ({
|
|
32
77
|
TARGETS
|
|
33
78
|
})
|
|
34
79
|
}), templateOnly());
|
|
80
|
+
/**
|
|
81
|
+
* For manually registering a PortalTarget for use with Portal
|
|
82
|
+
*/
|
|
83
|
+
const PortalTarget = setComponentTemplate(precompileTemplate("\n <div {{register @name}} ...attributes></div>\n", {
|
|
84
|
+
strictMode: true,
|
|
85
|
+
scope: () => ({
|
|
86
|
+
register
|
|
87
|
+
})
|
|
88
|
+
}), templateOnly());
|
|
35
89
|
|
|
36
|
-
export { PortalTargets, TARGETS, PortalTargets as default, findNearestTarget };
|
|
90
|
+
export { PortalTarget, PortalTargets, TARGETS, PortalTargets as default, findNearestTarget };
|
|
37
91
|
//# sourceMappingURL=portal-targets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portal-targets.js","sources":["../../src/components/portal-targets.gts"],"sourcesContent":["import { assert } from \"@ember/debug\";\nimport { isDevelopingApp, macroCondition } from \"@embroider/macros\";\n\nimport type { TOC } from \"@ember/component/template-only\";\n\nexport const TARGETS = Object.freeze({\n popover: \"ember-primitives__portal-targets__popover\",\n tooltip: \"ember-primitives__portal-targets__tooltip\",\n modal: \"ember-primitives__portal-targets__modal\",\n});\n\nexport function findNearestTarget(origin: Element, name: string) {\n assert(`first argument to \\`findNearestTarget\\` must be an element`, origin instanceof Element);\n assert(`second argument to \\`findNearestTarget\\` must be a string`, typeof name === `string`);\n\n let element: Element | null = null;\n\n let parent = origin.parentNode;\n\n
|
|
1
|
+
{"version":3,"file":"portal-targets.js","sources":["../../src/components/portal-targets.gts"],"sourcesContent":["import { assert } from \"@ember/debug\";\nimport { isDevelopingApp, macroCondition } from \"@embroider/macros\";\n\nimport { modifier } from \"ember-modifier\";\nimport { TrackedMap, TrackedSet } from \"tracked-built-ins\";\n\nimport type { TOC } from \"@ember/component/template-only\";\n\nconst cache = new TrackedMap<string, Set<Element>>();\n\nexport const TARGETS = Object.freeze({\n popover: \"ember-primitives__portal-targets__popover\",\n tooltip: \"ember-primitives__portal-targets__tooltip\",\n modal: \"ember-primitives__portal-targets__modal\",\n});\n\nexport function findNearestTarget(origin: Element, name: string): Element | undefined {\n assert(`first argument to \\`findNearestTarget\\` must be an element`, origin instanceof Element);\n assert(`second argument to \\`findNearestTarget\\` must be a string`, typeof name === `string`);\n\n let element: Element | undefined | null = null;\n\n let parent = origin.parentNode;\n\n const manuallyRegisteredSet = cache.get(name);\n const manuallyRegistered: Element[] | null = manuallyRegisteredSet?.size\n ? [...manuallyRegisteredSet]\n : null;\n\n /**\n * For use with <PortalTarget @name=\"hi\" />\n */\n function findRegistered(host: ParentNode): Element | undefined {\n return manuallyRegistered?.find((element) => {\n if (host.contains(element)) {\n return element;\n }\n });\n }\n\n const selector = Object.values(TARGETS as Record<string, string>).includes(name)\n ? `[data-portal-name=${name}]`\n : name;\n\n /**\n * Default portals / non-registered -- here we match a query selector instead of an element\n */\n function findDefault(host: ParentNode): Element | undefined {\n return host.querySelector(selector) as Element;\n }\n\n const finder = manuallyRegistered ? findRegistered : findDefault;\n\n /**\n * Crawl up the ancestry looking for our portal target\n */\n while (!element && parent) {\n element = finder(parent);\n if (element) break;\n parent = parent.parentNode;\n }\n\n if (macroCondition(isDevelopingApp())) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n (window as any).prime0 = origin;\n }\n\n if (name.startsWith(\"ember-primitives\")) {\n assert(\n `Could not find element by the given name: \\`${name}\\`.` +\n ` The known names are ` +\n `${Object.values(TARGETS).join(\", \")} ` +\n `-- but any name will work as long as it is set to the \\`data-portal-name\\` attribute ` +\n `(or if the name has been specifically registered via the <PortalTarget /> component). ` +\n `Double check that the element you're wanting to portal to is rendered. ` +\n `The element passed to \\`findNearestTarget\\` is stored on \\`window.prime0\\` ` +\n `You can debug in your browser's console via ` +\n `\\`document.querySelector('[data-portal-name=\"${name}\"]')\\``,\n element,\n );\n }\n\n return element ?? undefined;\n}\n\nconst register = modifier((element: Element, [name]: [name: string]) => {\n assert(`@name is required when using <PortalTarget>`, name);\n\n void (async () => {\n // Bad TypeScript lint.\n // eslint-disable-next-line @typescript-eslint/await-thenable\n await 0;\n\n let existing = cache.get(name);\n\n if (!existing) {\n existing = new TrackedSet<Element>();\n cache.set(name, existing);\n }\n\n existing.add(element);\n })();\n\n return () => {\n cache.delete(name);\n };\n});\n\nexport interface Signature {\n Element: null;\n}\n\nexport const PortalTargets: TOC<Signature> = <template>\n <div data-portal-name={{TARGETS.popover}}></div>\n <div data-portal-name={{TARGETS.tooltip}}></div>\n <div data-portal-name={{TARGETS.modal}}></div>\n</template>;\n\n/**\n * For manually registering a PortalTarget for use with Portal\n */\nexport const PortalTarget: TOC<{\n Element: HTMLDivElement;\n Args: {\n /**\n * The name of the PortalTarget\n *\n * This exact string may be passed to `Portal`'s `@to` argument.\n */\n name: string;\n };\n}> = <template>\n <div {{register @name}} ...attributes></div>\n</template>;\n\nexport default PortalTargets;\n"],"names":["cache","TrackedMap","TARGETS","Object","freeze","popover","tooltip","modal","findNearestTarget","origin","name","assert","Element","element","parent","parentNode","manuallyRegisteredSet","get","manuallyRegistered","size","findRegistered","host","find","contains","selector","values","includes","findDefault","querySelector","finder","macroCondition","isDevelopingApp","window","prime0","startsWith","join","undefined","register","modifier","existing","TrackedSet","set","add","delete","PortalTargets","setComponentTemplate","precompileTemplate","strictMode","scope","templateOnly","PortalTarget"],"mappings":"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"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { assert } from '@ember/debug';
|
|
2
|
+
import { schedule } from '@ember/runloop';
|
|
3
|
+
import { buildWaiter } from '@ember/test-waiters';
|
|
2
4
|
import { modifier } from 'ember-modifier';
|
|
3
|
-
import { cell } from 'ember-resources';
|
|
5
|
+
import { resourceFactory, cell, resource } from 'ember-resources';
|
|
6
|
+
import { isElement } from '../narrowing.js';
|
|
4
7
|
import { findNearestTarget } from './portal-targets.js';
|
|
5
8
|
import { precompileTemplate } from '@ember/template-compilation';
|
|
6
9
|
import { setComponentTemplate } from '@ember/component';
|
|
@@ -26,12 +29,11 @@ import templateOnly from '@ember/component/template-only';
|
|
|
26
29
|
*/
|
|
27
30
|
function wormhole(query) {
|
|
28
31
|
assert(`Expected query/element to be truthy.`, query);
|
|
29
|
-
if (query
|
|
32
|
+
if (isElement(query)) {
|
|
30
33
|
return query;
|
|
31
34
|
}
|
|
32
35
|
let found = document.getElementById(query);
|
|
33
36
|
found ??= document.querySelector(query);
|
|
34
|
-
assert(`Could not find element with id/selector ${query}`, found);
|
|
35
37
|
return found;
|
|
36
38
|
}
|
|
37
39
|
const anchor = modifier((element, [to, update]) => {
|
|
@@ -41,13 +43,40 @@ const anchor = modifier((element, [to, update]) => {
|
|
|
41
43
|
update(found);
|
|
42
44
|
});
|
|
43
45
|
const ElementValue = () => cell();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
const waiter = buildWaiter("ember-primitives:portal");
|
|
47
|
+
function wormholeCompat(selector) {
|
|
48
|
+
const target = wormhole(selector);
|
|
49
|
+
if (target) return target;
|
|
50
|
+
return resource(() => {
|
|
51
|
+
const target = cell();
|
|
52
|
+
const token = waiter.beginAsync();
|
|
53
|
+
// eslint-disable-next-line ember/no-runloop
|
|
54
|
+
schedule("afterRender", () => {
|
|
55
|
+
const result = wormhole(selector);
|
|
56
|
+
waiter.endAsync(token);
|
|
57
|
+
target.current = result;
|
|
58
|
+
assert(`Could not find element with id/selector \`${typeof selector === "string" ? selector : "<Element>"}\``, result);
|
|
59
|
+
});
|
|
60
|
+
return () => target.current;
|
|
61
|
+
});
|
|
46
62
|
}
|
|
47
|
-
|
|
63
|
+
resourceFactory(wormholeCompat);
|
|
64
|
+
const Portal = setComponentTemplate(precompileTemplate("\n {{#if (isElement @to)}}\n <ToElement @to={{@to}} @append={{@append}}>\n {{yield}}\n </ToElement>\n {{else if @wormhole}}\n {{#let (wormholeCompat @wormhole) as |target|}}\n {{#if target}}\n {{#in-element target insertBefore=null}}\n {{yield}}\n {{/in-element}}\n {{/if}}\n {{/let}}\n {{else if @to}}\n <Nestable @to={{@to}} @append={{@append}}>\n {{yield}}\n </Nestable>\n {{else}}\n {{assert \"either @to or @wormhole is required. Received neither\"}}\n {{/if}}\n", {
|
|
48
65
|
strictMode: true,
|
|
49
66
|
scope: () => ({
|
|
50
67
|
isElement,
|
|
68
|
+
ToElement,
|
|
69
|
+
wormholeCompat,
|
|
70
|
+
Nestable,
|
|
71
|
+
assert
|
|
72
|
+
})
|
|
73
|
+
}), templateOnly());
|
|
74
|
+
const ToElement = setComponentTemplate(precompileTemplate("\n {{#if @append}}\n {{#in-element @to insertBefore=null}}\n {{yield}}\n {{/in-element}}\n {{else}}\n {{#in-element @to}}\n {{yield}}\n {{/in-element}}\n {{/if}}\n", {
|
|
75
|
+
strictMode: true
|
|
76
|
+
}), templateOnly());
|
|
77
|
+
const Nestable = setComponentTemplate(precompileTemplate("\n {{#let (ElementValue) as |target|}}\n {{!-- This div is always going to be empty,\n because it'll either find the portal and render content elsewhere,\n it it won't find the portal and won't render anything.\n --}}\n {{!-- template-lint-disable no-inline-styles --}}\n <div style=\"display:contents;\" {{anchor @to target.set}}>\n {{#if target.current}}\n {{#if @append}}\n {{#in-element target.current insertBefore=null}}\n {{yield}}\n {{/in-element}}\n {{else}}\n {{#in-element target.current}}\n {{yield}}\n {{/in-element}}\n {{/if}}\n {{/if}}\n </div>\n {{/let}}\n", {
|
|
78
|
+
strictMode: true,
|
|
79
|
+
scope: () => ({
|
|
51
80
|
ElementValue,
|
|
52
81
|
anchor
|
|
53
82
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portal.js","sources":["../../src/components/portal.gts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-redundant-type-constituents */\nimport { assert } from \"@ember/debug\";\n\nimport { modifier } from \"ember-modifier\";\nimport { cell } from \"ember-resources\";\n\nimport { findNearestTarget, type TARGETS } from \"./portal-targets.gts\";\n\nimport type { TOC } from \"@ember/component/template-only\";\n\ntype Targets = (typeof TARGETS)[keyof typeof TARGETS];\n\nexport interface Signature {\n Args: {\n /**\n * The name of the PortalTarget to render in to.\n * This is the value of the `data-portal-name` attribute\n * of the element you wish to render in to.\n *\n * This can also be an Element which pairs nicely with query-utilities such as
|
|
1
|
+
{"version":3,"file":"portal.js","sources":["../../src/components/portal.gts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-redundant-type-constituents */\nimport { assert } from \"@ember/debug\";\nimport { schedule } from \"@ember/runloop\";\nimport { buildWaiter } from \"@ember/test-waiters\";\n\nimport { modifier } from \"ember-modifier\";\nimport { cell, resource, resourceFactory } from \"ember-resources\";\n\nimport { isElement } from \"../narrowing.ts\";\nimport { findNearestTarget, type TARGETS } from \"./portal-targets.gts\";\n\nimport type { TOC } from \"@ember/component/template-only\";\n\ntype Targets = (typeof TARGETS)[keyof typeof TARGETS];\n\ninterface ToSignature {\n Args: {\n to: string;\n append?: boolean;\n };\n Blocks: {\n default: [];\n };\n}\ninterface ElementSignature {\n Args: {\n to: Element;\n append?: boolean;\n };\n Blocks: {\n default: [];\n };\n}\n\nexport interface Signature {\n Args: {\n /**\n * The name of the PortalTarget to render in to.\n * This is the value of the `data-portal-name` attribute\n * of the element you wish to render in to.\n *\n * This can also be an Element which pairs nicely with query-utilities such as the platform-native `querySelector`\n */\n to?: (Targets | (string & {})) | Element;\n\n /**\n * Set to true to append to the portal instead of replace\n *\n * Default: false\n */\n append?: boolean;\n /**\n * For ember-wormhole style behavior, this argument may be an id,\n * or a selector.\n * This can also be an element, in which case the behavior is identical to `@to`\n */\n wormhole?: string | Element;\n };\n Blocks: {\n /**\n * The portaled content\n */\n default: [];\n };\n}\n\n/**\n * Polyfill for ember-wormhole behavior\n *\n * Example usage:\n * ```gjs\n * import { wormhole, Portal } from 'ember-primitives/components/portal';\n *\n * <template>\n * <div id=\"the-portal\"></div>\n *\n * <Portal @to={{wormhole \"the-portal\"}}>\n * content renders in the above div\n * </Portal>\n * </template>\n *\n * ```\n */\nexport function wormhole(query: string | null | undefined | Element) {\n assert(`Expected query/element to be truthy.`, query);\n\n if (isElement(query)) {\n return query;\n }\n\n let found = document.getElementById(query);\n\n found ??= document.querySelector(query);\n\n return found;\n}\n\nconst anchor = modifier(\n (element: Element, [to, update]: [string, ReturnType<typeof ElementValue>[\"set\"]]) => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call\n const found = findNearestTarget(element, to);\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n update(found);\n },\n);\n\nconst ElementValue = () => cell<Element | ShadowRoot | null | undefined>();\n\nconst waiter = buildWaiter(\"ember-primitives:portal\");\n\nfunction wormholeCompat(selector: string | Element) {\n const target = wormhole(selector);\n\n if (target) return target;\n\n return resource(() => {\n const target = cell<Element | undefined | null>();\n\n const token = waiter.beginAsync();\n\n // eslint-disable-next-line ember/no-runloop\n schedule(\"afterRender\", () => {\n const result = wormhole(selector);\n\n waiter.endAsync(token);\n target.current = result;\n assert(\n `Could not find element with id/selector \\`${typeof selector === \"string\" ? selector : \"<Element>\"}\\``,\n result,\n );\n });\n\n return () => target.current;\n });\n}\n\nresourceFactory(wormholeCompat);\n\nexport const Portal: TOC<Signature> = <template>\n {{#if (isElement @to)}}\n <ToElement @to={{@to}} @append={{@append}}>\n {{yield}}\n </ToElement>\n {{else if @wormhole}}\n {{#let (wormholeCompat @wormhole) as |target|}}\n {{#if target}}\n {{#in-element target insertBefore=null}}\n {{yield}}\n {{/in-element}}\n {{/if}}\n {{/let}}\n {{else if @to}}\n <Nestable @to={{@to}} @append={{@append}}>\n {{yield}}\n </Nestable>\n {{else}}\n {{assert \"either @to or @wormhole is required. Received neither\"}}\n {{/if}}\n</template>;\n\nconst ToElement: TOC<ElementSignature> = <template>\n {{#if @append}}\n {{#in-element @to insertBefore=null}}\n {{yield}}\n {{/in-element}}\n {{else}}\n {{#in-element @to}}\n {{yield}}\n {{/in-element}}\n {{/if}}\n</template>;\n\nconst Nestable: TOC<ToSignature> = <template>\n {{#let (ElementValue) as |target|}}\n {{! This div is always going to be empty,\n because it'll either find the portal and render content elsewhere,\n it it won't find the portal and won't render anything.\n }}\n {{! template-lint-disable no-inline-styles }}\n <div style=\"display:contents;\" {{anchor @to target.set}}>\n {{#if target.current}}\n {{#if @append}}\n {{#in-element target.current insertBefore=null}}\n {{yield}}\n {{/in-element}}\n {{else}}\n {{#in-element target.current}}\n {{yield}}\n {{/in-element}}\n {{/if}}\n {{/if}}\n </div>\n {{/let}}\n</template>;\n\nexport default Portal;\n"],"names":["wormhole","query","assert","isElement","found","document","getElementById","querySelector","anchor","modifier","element","to","update","findNearestTarget","ElementValue","cell","waiter","buildWaiter","wormholeCompat","selector","target","resource","token","beginAsync","schedule","result","endAsync","current","resourceFactory","Portal","setComponentTemplate","precompileTemplate","strictMode","scope","ToElement","Nestable","templateOnly"],"mappings":";;;;;;;;;;;AAAA;AAkEA;;;;;;;;;;;;;;;;;AAiBO,SAASA,QAAAA,CAASC,KAA0C,EAAA;AACjEC,EAAAA,MAAA,CAAO,CAAA,oCAAA,CAAsC,EAAED,KAAA,CAAA;AAE/C,EAAA,IAAIE,UAAUF,KAAA,CAAA,EAAQ;AACpB,IAAA,OAAOA,KAAA;AACT,EAAA;AAEA,EAAA,IAAIG,KAAA,GAAQC,QAAA,CAASC,cAAc,CAACL,KAAA,CAAA;AAEpCG,EAAAA,KAAA,KAAUC,QAAA,CAASE,aAAa,CAACN,KAAA,CAAA;AAEjC,EAAA,OAAOG,KAAA;AACT;AAEA,MAAMI,MAAA,GAASC,QAAA,CACb,CAACC,OAAS,EAAS,CAACC,EAAA,EAAIC,MAAA,CAAyD,KAAA;AAC/E;AACA,EAAA,MAAMR,KAAA,GAAQS,kBAAkBH,OAAA,EAASC,EAAA,CAAA;AAEzC;EACAC,MAAA,CAAOR,KAAA,CAAA;AACT,CAAA,CAAA;AAGF,MAAMU,eAAeA,MAAMC,IAAA,EAA4C;AAEvE,MAAMC,SAASC,WAAA,CAAY,yBAAA,CAAA;AAE3B,SAASC,cAAAA,CAAeC,QAA0B,EAAA;AAChD,EAAA,MAAMC,SAASpB,QAAA,CAASmB,QAAA,CAAA;EAExB,IAAIC,QAAQ,OAAOA,MAAA;EAEnB,OAAOC,QAAA,CAAS,MAAA;AACd,IAAA,MAAMD,MAAA,GAASL,IAAA,EAA+B;AAE9C,IAAA,MAAMO,KAAA,GAAQN,OAAOO,UAAU,EAAA;AAE/B;IACAC,QAAA,CAAS,aAAA,EAAe,MAAA;AACtB,MAAA,MAAMC,SAASzB,QAAA,CAASmB,QAAA,CAAA;AAExBH,MAAAA,MAAA,CAAOU,QAAQ,CAACJ,KAAA,CAAA;MAChBF,MAAA,CAAOO,OAAO,GAAGF,MAAA;AACjBvB,MAAAA,MAAA,CACE,CAAA,0CAAA,EAA6C,OAAOiB,QAAA,KAAa,WAAWA,QAAA,GAAW,WAAA,CAAA,EAAA,CAAe,EACtGM,MAAA,CAAA;AAEJ,IAAA,CAAA,CAAA;IAEA,OAAO,MAAML,OAAOO,OAAO;AAC7B,EAAA,CAAA,CAAA;AACF;AAEAC,eAAA,CAAgBV,cAAA,CAAA;MAEHW,MAAY,GAAAC,oBAAA,CAAaC,kBAAA,CAAA,0hBAAA,EAoBtC;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAA9B,SAAA;IAAA+B,SAAA;IAAAhB,cAAA;IAAAiB,QAAA;AAAAjC,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAAkC,YAAA,EAAA;AAEV,MAAMF,SAAe,GAAAJ,oBAAA,CAAoBC,kBAAA,CAAA,8LAAA,EAUzC;EAAAC,UAAA,EAAA;AAAU,CAAA,CAAA,EAAAI,YAAA,EAAA,CAAA;AAEV,MAAMD,QAAc,GAAAL,oBAAA,CAAeC,kBAAA,CAAA,2rBAAA,EAqBnC;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAnB,YAAA;AAAAN,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAA4B,YAAA,EAAA,CAAA;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrowing.js","sources":["../src/narrowing.ts"],"sourcesContent":["export function isString(x: unknown): x is string {\n return typeof x === 'string';\n}\n\nexport function isElement(x: unknown): x is Element {\n return x instanceof Element;\n}\n"],"names":["isString","x","isElement","Element"],"mappings":"AAAO,SAASA,QAAQA,CAACC,CAAU,EAAe;EAChD,OAAO,OAAOA,CAAC,KAAK,QAAQ;AAC9B;AAEO,SAASC,SAASA,CAACD,CAAU,EAAgB;EAClD,OAAOA,CAAC,YAAYE,OAAO;AAC7B;;;;"}
|
package/dist/proper-links.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proper-links.js","sources":["../src/proper-links.ts"],"sourcesContent":["import { assert } from '@ember/debug';\nimport { registerDestructor } from '@ember/destroyable';\nimport { getOwner } from '@ember/owner';\n\nimport { getAnchor, shouldHandle } from 'should-handle-link';\n\nimport type EmberRouter from '@ember/routing/router';\nimport type RouterService from '@ember/routing/router-service';\n\nexport { shouldHandle } from 'should-handle-link';\n\
|
|
1
|
+
{"version":3,"file":"proper-links.js","sources":["../src/proper-links.ts"],"sourcesContent":["import { assert } from '@ember/debug';\nimport { registerDestructor } from '@ember/destroyable';\nimport { getOwner } from '@ember/owner';\n\nimport { getAnchor, shouldHandle } from 'should-handle-link';\n\nimport type { Newable } from './type-utils.ts';\nimport type EmberRouter from '@ember/routing/router';\nimport type RouterService from '@ember/routing/router-service';\n\nexport { shouldHandle } from 'should-handle-link';\n\nexport interface Options {\n ignore?: string[];\n}\n\nexport function properLinks(\n options: Options\n): <Instance extends object, Klass = { new (...args: any[]): Instance }>(klass: Klass) => Klass;\n\nexport function properLinks<Instance extends object, Klass = { new (...args: any[]): Instance }>(\n klass: Klass\n): Klass;\n/**\n * @internal\n */\nexport function properLinks<Instance extends object, Klass = { new (...args: any[]): Instance }>(\n options: Options,\n klass: Klass\n): Klass;\n\nexport function properLinks<Instance extends object, Klass = { new (...args: any[]): Instance }>(\n ...args: [Options] | [Klass] | [Options, Klass]\n): Klass | ((klass: Klass) => Klass) {\n let options: Options = {};\n\n let klass: undefined | Klass = undefined;\n\n if (args.length === 2) {\n options = args[0];\n klass = args[1];\n } else if (args.length === 1) {\n if (typeof args[0] === 'object') {\n // TODO: how to get first arg type correct?\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n return (klass: Klass) => properLinks(args[0] as any, klass);\n } else {\n klass = args[0];\n }\n }\n\n const ignore = options.ignore || [];\n\n assert(`klass was not defined. possibile incorrect arity given to properLinks`, klass);\n\n return class RouterWithProperLinks extends (klass as unknown as Newable<EmberRouter>) {\n // SAFETY: we literally do not care about the args' type here,\n // because we just call super\n constructor(...args: any[]) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n super(...args);\n\n setup(this, ignore);\n }\n } as unknown as Klass;\n}\n\n/**\n * Setup proper links without a decorator.\n * This function only requires that a framework object with an owner is passed.\n */\nexport function setup(parent: object, ignore?: string[]) {\n const handler = (event: MouseEvent) => {\n /**\n * event.target may not be an anchor,\n * it may be a span, svg, img, or any number of elements nested in <a>...</a>\n */\n const interactive = getAnchor(event);\n\n if (!interactive) return;\n\n const owner = getOwner(parent);\n\n assert('owner is not present', owner);\n\n const routerService = owner.lookup('service:router');\n\n handle(routerService, interactive, ignore ?? [], event);\n };\n\n document.body.addEventListener('click', handler, false);\n\n registerDestructor(parent, () => document.body.removeEventListener('click', handler));\n}\n\nexport function handle(\n router: RouterService,\n element: HTMLAnchorElement,\n ignore: string[],\n event: MouseEvent\n) {\n if (!shouldHandle(location.href, element, event, ignore)) {\n return;\n }\n\n const url = new URL(element.href);\n\n const fullHref = `${url.pathname}${url.search}${url.hash}`;\n\n const rootURL = router.rootURL;\n\n let withoutRootURL = fullHref.slice(rootURL.length);\n\n // re-add the \"root\" sigil\n // we removed it when we chopped off the rootURL,\n // because the rootURL often has this attached to it as well\n if (!withoutRootURL.startsWith('/')) {\n withoutRootURL = `/${withoutRootURL}`;\n }\n\n try {\n const routeInfo = router.recognize(fullHref);\n\n if (routeInfo) {\n event.preventDefault();\n event.stopImmediatePropagation();\n event.stopPropagation();\n\n router.transitionTo(withoutRootURL);\n\n return false;\n }\n } catch (e) {\n if (e instanceof Error && e.name === 'UnrecognizedURLError') {\n return;\n }\n\n throw e;\n }\n}\n"],"names":["properLinks","args","options","klass","undefined","length","ignore","assert","RouterWithProperLinks","constructor","setup","parent","handler","event","interactive","getAnchor","owner","getOwner","routerService","lookup","handle","document","body","addEventListener","registerDestructor","removeEventListener","router","element","shouldHandle","location","href","url","URL","fullHref","pathname","search","hash","rootURL","withoutRootURL","slice","startsWith","routeInfo","recognize","preventDefault","stopImmediatePropagation","stopPropagation","transitionTo","e","Error","name"],"mappings":"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"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { link } from 'reactiveweb/link';
|
|
2
|
+
import { isNewable } from './utils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* context => { class => instance }
|
|
6
|
+
*/
|
|
7
|
+
const contextCache = new WeakMap();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a singleton for the given context and links the lifetime of the created class to the passed context
|
|
11
|
+
*
|
|
12
|
+
* Note that this function is _not_ lazy. Calling `createStore` will create an instance of the passed class.
|
|
13
|
+
* When combined with a getter though, creation becomes lazy.
|
|
14
|
+
*
|
|
15
|
+
* In this example, `MyState` is created once per instance of the component.
|
|
16
|
+
* repeat accesses to `this.foo` return a stable reference _as if_ `@cached` were used.
|
|
17
|
+
* ```js
|
|
18
|
+
* class MyState {}
|
|
19
|
+
*
|
|
20
|
+
* class Demo extends Component {
|
|
21
|
+
* // this is a stable reference
|
|
22
|
+
* get foo() {
|
|
23
|
+
* return createStore(this, MyState);
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* // or
|
|
27
|
+
* bar = createStore(this, MyState);
|
|
28
|
+
*
|
|
29
|
+
* // or
|
|
30
|
+
* three = createStore(this, () => new MyState(1, 2));
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* If arguments need to be configured during construction, the second argument may also be a function
|
|
35
|
+
* ```js
|
|
36
|
+
* class MyState {}
|
|
37
|
+
*
|
|
38
|
+
* class Demo extends Component {
|
|
39
|
+
* // this is a stable reference
|
|
40
|
+
* get foo() {
|
|
41
|
+
* return createStore(this, MyState);
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
function createStore(context, theClass) {
|
|
47
|
+
let cache = contextCache.get(context);
|
|
48
|
+
if (!cache) {
|
|
49
|
+
cache = new Map();
|
|
50
|
+
contextCache.set(context, cache);
|
|
51
|
+
}
|
|
52
|
+
let existing = cache.get(theClass);
|
|
53
|
+
if (!existing) {
|
|
54
|
+
const instance = isNewable(theClass) ? new theClass() : theClass();
|
|
55
|
+
link(instance, context);
|
|
56
|
+
cache.set(theClass, instance);
|
|
57
|
+
existing = instance;
|
|
58
|
+
}
|
|
59
|
+
return existing;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { createStore };
|
|
63
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sources":["../src/store.ts"],"sourcesContent":["import { link } from 'reactiveweb/link';\n\nimport { isNewable } from './utils.ts';\n\nimport type { Newable } from './type-utils.ts';\n\n/**\n * context => { class => instance }\n */\nconst contextCache = new WeakMap<object, Map<object, object>>();\n\n/**\n * Creates a singleton for the given context and links the lifetime of the created class to the passed context\n *\n * Note that this function is _not_ lazy. Calling `createStore` will create an instance of the passed class.\n * When combined with a getter though, creation becomes lazy.\n *\n * In this example, `MyState` is created once per instance of the component.\n * repeat accesses to `this.foo` return a stable reference _as if_ `@cached` were used.\n * ```js\n * class MyState {}\n *\n * class Demo extends Component {\n * // this is a stable reference\n * get foo() {\n * return createStore(this, MyState);\n * }\n *\n * // or\n * bar = createStore(this, MyState);\n *\n * // or\n * three = createStore(this, () => new MyState(1, 2));\n * }\n * ```\n *\n * If arguments need to be configured during construction, the second argument may also be a function\n * ```js\n * class MyState {}\n *\n * class Demo extends Component {\n * // this is a stable reference\n * get foo() {\n * return createStore(this, MyState);\n * }\n * }\n * ```\n */\nexport function createStore<Instance extends object>(\n context: object,\n theClass: Newable<Instance> | (() => Instance)\n): Instance {\n let cache = contextCache.get(context);\n\n if (!cache) {\n cache = new Map();\n contextCache.set(context, cache);\n }\n\n let existing = cache.get(theClass);\n\n if (!existing) {\n const instance = isNewable(theClass) ? new theClass() : theClass();\n\n link(instance, context);\n\n cache.set(theClass, instance);\n existing = instance;\n }\n\n return existing as Instance;\n}\n"],"names":["contextCache","WeakMap","createStore","context","theClass","cache","get","Map","set","existing","instance","isNewable","link"],"mappings":";;;AAMA;AACA;AACA;AACA,MAAMA,YAAY,GAAG,IAAIC,OAAO,EAA+B;;AAE/D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,WAAWA,CACzBC,OAAe,EACfC,QAA8C,EACpC;AACV,EAAA,IAAIC,KAAK,GAAGL,YAAY,CAACM,GAAG,CAACH,OAAO,CAAC;EAErC,IAAI,CAACE,KAAK,EAAE;AACVA,IAAAA,KAAK,GAAG,IAAIE,GAAG,EAAE;AACjBP,IAAAA,YAAY,CAACQ,GAAG,CAACL,OAAO,EAAEE,KAAK,CAAC;AAClC,EAAA;AAEA,EAAA,IAAII,QAAQ,GAAGJ,KAAK,CAACC,GAAG,CAACF,QAAQ,CAAC;EAElC,IAAI,CAACK,QAAQ,EAAE;AACb,IAAA,MAAMC,QAAQ,GAAGC,SAAS,CAACP,QAAQ,CAAC,GAAG,IAAIA,QAAQ,EAAE,GAAGA,QAAQ,EAAE;AAElEQ,IAAAA,IAAI,CAACF,QAAQ,EAAEP,OAAO,CAAC;AAEvBE,IAAAA,KAAK,CAACG,GAAG,CAACJ,QAAQ,EAAEM,QAAQ,CAAC;AAC7BD,IAAAA,QAAQ,GAAGC,QAAQ;AACrB,EAAA;AAEA,EAAA,OAAOD,QAAQ;AACjB;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-utils.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/utils.js
CHANGED
|
@@ -7,6 +7,10 @@ function uniqueId() {
|
|
|
7
7
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-unsafe-member-access
|
|
8
8
|
return ([3e7] + -1e3 + -4e3 + -2e3 + -1e11).replace(/[0-3]/g, a => (a * 4 ^ Math.random() * 16 >> (a & 2)).toString(16));
|
|
9
9
|
}
|
|
10
|
+
function isNewable(x) {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
12
|
+
return x.prototype?.constructor === x;
|
|
13
|
+
}
|
|
10
14
|
|
|
11
|
-
export { uniqueId };
|
|
15
|
+
export { isNewable, uniqueId };
|
|
12
16
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["// this is copy pasted from https://github.com/emberjs/ember.js/blob/60d2e0cddb353aea0d6e36a72fda971010d92355/packages/%40ember/-internals/glimmer/lib/helpers/unique-id.ts\n// Unfortunately due to https://github.com/emberjs/ember.js/issues/20165 we cannot use the built-in version in template tags\nexport function uniqueId(): string {\n // @ts-expect-error this one-liner abuses weird JavaScript semantics that\n // TypeScript (legitimately) doesn't like, but they're nonetheless valid and\n // specced.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-unsafe-member-access\n return ([3e7] + -1e3 + -4e3 + -2e3 + -1e11).replace(/[0-3]/g, (a) =>\n ((a * 4) ^ ((Math.random() * 16) >> (a & 2))).toString(16)\n );\n}\n"],"names":["uniqueId","replace","a","Math","random","toString"],"mappings":"AAAA;AACA;AACO,SAASA,QAAQA,GAAW;AACjC;AACA;AACA;AACA;EACA,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAEC,OAAO,CAAC,QAAQ,EAAGC,CAAC,IAC9D,CAAEA,CAAC,GAAG,CAAC,GAAMC,IAAI,CAACC,MAAM,EAAE,GAAG,EAAE,KAAMF,CAAC,GAAG,CAAC,CAAE,EAAEG,QAAQ,CAAC,EAAE,CAC3D,CAAC;AACH;;;;"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["// this is copy pasted from https://github.com/emberjs/ember.js/blob/60d2e0cddb353aea0d6e36a72fda971010d92355/packages/%40ember/-internals/glimmer/lib/helpers/unique-id.ts\n// Unfortunately due to https://github.com/emberjs/ember.js/issues/20165 we cannot use the built-in version in template tags\nexport function uniqueId(): string {\n // @ts-expect-error this one-liner abuses weird JavaScript semantics that\n // TypeScript (legitimately) doesn't like, but they're nonetheless valid and\n // specced.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-unsafe-member-access\n return ([3e7] + -1e3 + -4e3 + -2e3 + -1e11).replace(/[0-3]/g, (a) =>\n ((a * 4) ^ ((Math.random() * 16) >> (a & 2))).toString(16)\n );\n}\n\nexport function isNewable(x: any): x is new (...args: unknown[]) => NonNullable<object> {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n return x.prototype?.constructor === x;\n}\n"],"names":["uniqueId","replace","a","Math","random","toString","isNewable","x","prototype","constructor"],"mappings":"AAAA;AACA;AACO,SAASA,QAAQA,GAAW;AACjC;AACA;AACA;AACA;EACA,OAAO,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAEC,OAAO,CAAC,QAAQ,EAAGC,CAAC,IAC9D,CAAEA,CAAC,GAAG,CAAC,GAAMC,IAAI,CAACC,MAAM,EAAE,GAAG,EAAE,KAAMF,CAAC,GAAG,CAAC,CAAE,EAAEG,QAAQ,CAAC,EAAE,CAC3D,CAAC;AACH;AAEO,SAASC,SAASA,CAACC,CAAM,EAAwD;AACtF;AACA,EAAA,OAAOA,CAAC,CAACC,SAAS,EAAEC,WAAW,KAAKF,CAAC;AACvC;;;;"}
|