ember-primitives 0.38.0 → 0.40.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.
@@ -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 `wormhole`, or the platform-native `querySelector`
11
+ * This can also be an Element which pairs nicely with query-utilities such as the platform-native `querySelector`
12
12
  */
13
- to: (Targets | (string & {})) | Element;
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,58 @@
1
+ import Component from "@glimmer/component";
2
+ import type { Newable } from "./type-utils";
3
+ import type Owner from "@ember/owner";
4
+ export declare class Provide<Data extends object> extends Component<{
5
+ Args: {
6
+ /**
7
+ * What data do you want to provide to the DOM subtree?
8
+ *
9
+ * If this is a function or class, it will be instantiated and given an
10
+ * owner + destroyable linkage via `createStore`
11
+ */
12
+ data: Data | (() => Data) | Newable<Data>;
13
+ /**
14
+ * Optionally, you may use string-based keys to reference the data in the Provide.
15
+ *
16
+ * This is not recommended though, because when using a class or other object-like structure,
17
+ * the type in the `<Consume>` component can be derived from that class or object-like structure.
18
+ * With string keys, the `<Consume>` type will be unknown.
19
+ */
20
+ key?: string;
21
+ };
22
+ Blocks: {
23
+ /**
24
+ * The content that this component will _provide_ data to the entire hierarchy.
25
+ */
26
+ default: [];
27
+ };
28
+ }> {
29
+ get data(): Data;
30
+ element: HTMLDivElement;
31
+ constructor(owner: Owner, args: {
32
+ data: Data | (() => Data) | Newable<Data>;
33
+ key?: string;
34
+ });
35
+ }
36
+ type DataForKey<Key> = Key extends string ? unknown : Key extends Newable<infer T> ? T : Key extends () => infer T ? T : Key;
37
+ export declare class Consume<Key extends object | string> extends Component<{
38
+ Args: {
39
+ key: Key;
40
+ };
41
+ Blocks: {
42
+ default: [
43
+ context: {
44
+ data: DataForKey<Key>;
45
+ }
46
+ ];
47
+ };
48
+ }> {
49
+ getData: () => DataForKey<Key>;
50
+ element: HTMLDivElement;
51
+ constructor(owner: Owner, args: {
52
+ key: Key;
53
+ });
54
+ get context(): {
55
+ readonly data: DataForKey<Key>;
56
+ };
57
+ }
58
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function isString(x: unknown): x is string;
2
+ export declare function isElement(x: unknown): x is Element;
@@ -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.querySelector(`[data-portal-name=${name}]`);
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
- 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. ` + `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);
27
- return element;
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 while (!element && parent) {\n element = parent.querySelector(`[data-portal-name=${name}]`);\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 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 `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 return element;\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\nexport default PortalTargets;\n"],"names":["TARGETS","Object","freeze","popover","tooltip","modal","findNearestTarget","origin","name","assert","Element","element","parent","parentNode","querySelector","macroCondition","isDevelopingApp","window","prime0","values","join","PortalTargets","setComponentTemplate","precompileTemplate","strictMode","scope","templateOnly"],"mappings":";;;;;;MAKaA,OAAA,GAAUC,MAAA,CAAOC,MAAM,CAAC;AACnCC,EAAAA,OAAA,EAAS,2CAAA;AACTC,EAAAA,OAAA,EAAS,2CAAA;AACTC,EAAAA,KAAA,EAAO;AACT,CAAA;AAEO,SAASC,iBAAAA,CAAkBC,MAAe,EAAEC,IAAY,EAAA;AAC7DC,EAAAA,MAAA,CAAO,CAAA,0DAAA,CAA4D,EAAEF,MAAA,YAAkBG,OAAA,CAAA;AACvFD,EAAAA,MAAA,CAAO,CAAA,yDAAA,CAA2D,EAAE,OAAOD,IAAA,KAAS,QAAQ,CAAA;EAE5F,IAAIG,OAAuB,GAAG,IAAA;AAE9B,EAAA,IAAIC,MAAA,GAASL,OAAOM,UAAU;AAE9B,EAAA,OAAO,CAACF,WAAWC,MAAA,EAAQ;IACzBD,OAAA,GAAUC,MAAA,CAAOE,aAAa,CAAC,CAAA,kBAAA,EAAqBN,IAAA,GAAO,CAAA;AAC3D,IAAA,IAAIG,OAAA,EAAS;IACbC,MAAA,GAASA,OAAOC,UAAU;AAC5B,EAAA;AAEA,EAAA,IAAIE,eAAeC,eAAA,EAAA,CAAA,EAAoB;AACrC;IACCC,MAAA,CAAeC,MAAM,GAAGX,MAAA;AAC3B,EAAA;AAEAE,EAAAA,MAAA,CACE,CAAA,4CAAA,EAA+CD,IAAA,CAAA,GAAA,CAAS,GACtD,CAAA,qBAAA,CAAuB,GACvB,CAAA,EAAGP,OAAOkB,MAAM,CAACnB,SAASoB,IAAI,CAAC,QAAQ,GACvC,CAAA,sFAAA,CAAwF,GACxF,yEAAyE,GACzE,CAAA,2EAAA,CAA6E,GAC7E,CAAA,4CAAA,CAA8C,GAC9C,CAAA,6CAAA,EAAgDZ,IAAA,CAAA,MAAA,CAAY,EAC9DG,OAAA,CAAA;AAGF,EAAA,OAAOA,OAAA;AACT;MAMaU,aAAmB,GAAAC,oBAAA,CAAaC,kBAAA,CAAA,8JAAA,EAI7C;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;AAAAzB,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAA0B,YAAA,EAAA;;;;"}
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 instanceof Element) {
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
- function isElement(x) {
45
- return x instanceof Element;
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
- const Portal = setComponentTemplate(precompileTemplate("\n {{#if (isElement @to)}}\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 {{else}}\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 {{/if}}\n", {
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 `wormhole`, or 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 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 (query instanceof Element) {\n return query;\n }\n\n let found = document.getElementById(query);\n\n found ??= document.querySelector(query);\n\n assert(`Could not find element with id/selector ${query}`, found);\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>();\n\nfunction isElement(x: unknown): x is Element {\n return x instanceof Element;\n}\n\nexport const Portal: TOC<Signature> = <template>\n {{#if (isElement @to)}}\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 {{else}}\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 {{/if}}\n</template>;\n\nexport default Portal;\n"],"names":["wormhole","query","assert","Element","found","document","getElementById","querySelector","anchor","modifier","element","to","update","findNearestTarget","ElementValue","cell","isElement","x","Portal","setComponentTemplate","precompileTemplate","strictMode","scope","templateOnly"],"mappings":";;;;;;;;AAAA;AAsCA;;;;;;;;;;;;;;;;;AAiBO,SAASA,QAAAA,CAASC,KAA0C,EAAA;AACjEC,EAAAA,MAAA,CAAO,CAAA,oCAAA,CAAsC,EAAED,KAAA,CAAA;EAE/C,IAAIA,iBAAiBE,OAAA,EAAS;AAC5B,IAAA,OAAOF,KAAA;AACT,EAAA;AAEA,EAAA,IAAIG,KAAA,GAAQC,QAAA,CAASC,cAAc,CAACL,KAAA,CAAA;AAEpCG,EAAAA,KAAA,KAAUC,QAAA,CAASE,aAAa,CAACN,KAAA,CAAA;AAEjCC,EAAAA,MAAA,CAAO,CAAA,wCAAA,EAA2CD,KAAA,CAAA,CAAO,EAAEG,KAAA,CAAA;AAE3D,EAAA,OAAOA,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,YAAA,GAAeA,MAAMC,IAAA,EAAe;AAE1C,SAASC,SAAAA,CAAUC,CAAU,EAAQ;EACnC,OAAOA,CAAA,YAAad,OAAA;AACtB;MAEae,MAAY,GAAAC,oBAAA,CAAaC,kBAAA,CAAA,29BAAA,EAiCtC;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAN,SAAA;IAAAF,YAAA;AAAAN,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAAe,YAAA,EAAA;;;;"}
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,86 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked, cached } from '@glimmer/tracking';
3
+ import { assert } from '@ember/debug';
4
+ import { createStore } from 'ember-primitives/store';
5
+ import { precompileTemplate } from '@ember/template-compilation';
6
+ import { setComponentTemplate } from '@ember/component';
7
+ import { g, i, n } from 'decorator-transforms/runtime';
8
+
9
+ const LOOKUP = new WeakMap();
10
+ class Provide extends Component {
11
+ get data() {
12
+ assert(`@data is missing in <Provide>. Please pass @data.`, "data" in this.args);
13
+ /**
14
+ * This covers both classes and functions
15
+ */
16
+ if (typeof this.args.data === "function") {
17
+ return createStore(this, this.args.data);
18
+ }
19
+ /**
20
+ * Non-instantiable value
21
+ */
22
+ return this.args.data;
23
+ }
24
+ element;
25
+ constructor(owner, args) {
26
+ super(owner, args);
27
+ const element = document.createElement("div");
28
+ element.style.display = "contents";
29
+ const key = this.args.key ?? this.args.data;
30
+ LOOKUP.set(element, [key, () => this.data]);
31
+ this.element = element;
32
+ }
33
+ static {
34
+ setComponentTemplate(precompileTemplate("\n {{this.element}}\n\n {{#in-element this.element}}\n {{yield}}\n {{/in-element}}\n ", {
35
+ strictMode: true
36
+ }), this);
37
+ }
38
+ }
39
+ function findForKey(startElement, key) {
40
+ let parent = startElement;
41
+ while (parent = parent.parentElement) {
42
+ const maybe = LOOKUP.get(parent);
43
+ if (!maybe) {
44
+ continue;
45
+ }
46
+ if (maybe[0] === key) {
47
+ return maybe[1];
48
+ }
49
+ }
50
+ }
51
+ class Consume extends Component {
52
+ static {
53
+ g(this.prototype, "getData", [tracked]);
54
+ }
55
+ #getData = (i(this, "getData"), void 0); // SAFETY: We do a runtime assert in the getter below.
56
+ element;
57
+ constructor(owner, args) {
58
+ super(owner, args);
59
+ this.element = document.createElement("div");
60
+ this.element.style.display = "contents";
61
+ }
62
+ get context() {
63
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
64
+ const self = this;
65
+ return {
66
+ get data() {
67
+ const getData = findForKey(self.element, self.args.key);
68
+ assert(`Could not find provided context in <Consume>. Please assure that there is a corresponding <Provide> component before using this <Consume> component`, getData);
69
+ // SAFETY: return type handled by getter's signature
70
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
71
+ return getData();
72
+ }
73
+ };
74
+ }
75
+ static {
76
+ n(this.prototype, "context", [cached]);
77
+ }
78
+ static {
79
+ setComponentTemplate(precompileTemplate("\n {{this.element}}\n\n {{#in-element this.element}}\n {{yield this.context}}\n {{/in-element}}\n ", {
80
+ strictMode: true
81
+ }), this);
82
+ }
83
+ }
84
+
85
+ export { Consume, Provide };
86
+ //# sourceMappingURL=dom-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-context.js","sources":["../src/dom-context.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\nimport { cached, tracked } from \"@glimmer/tracking\";\nimport { assert } from \"@ember/debug\";\n\nimport { createStore } from \"ember-primitives/store\";\n\nimport type { Newable } from \"./type-utils\";\nimport type Owner from \"@ember/owner\";\n\n/**\n * IMPLEMENTATION NOTE:\n * we don't use https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md\n * because it is not inherently reactive.\n *\n * Its *event* based, which opts you out of fine-grained reactivity.\n * We want minimal effort fine-grained reactivity.\n *\n * This Technique follows the DOM tree, and is synchronous,\n * allowing correct fine-grained signals-based reactivity.\n *\n * We *could* do less work to find Providers,\n * but only if we forgoe DOM-tree scoping.\n * We must traverse the DOM hierarchy to validate that we aren't accessing providers from different subtrees.\n */\nconst LOOKUP = new WeakMap<Element, [unknown, () => unknown]>();\n\nexport class Provide<Data extends object> extends Component<{\n Args: {\n /**\n * What data do you want to provide to the DOM subtree?\n *\n * If this is a function or class, it will be instantiated and given an\n * owner + destroyable linkage via `createStore`\n */\n data: Data | (() => Data) | Newable<Data>;\n\n /**\n * Optionally, you may use string-based keys to reference the data in the Provide.\n *\n * This is not recommended though, because when using a class or other object-like structure,\n * the type in the `<Consume>` component can be derived from that class or object-like structure.\n * With string keys, the `<Consume>` type will be unknown.\n */\n key?: string;\n };\n Blocks: {\n /**\n * The content that this component will _provide_ data to the entire hierarchy.\n */\n default: [];\n };\n}> {\n get data() {\n assert(`@data is missing in <Provide>. Please pass @data.`, \"data\" in this.args);\n\n /**\n * This covers both classes and functions\n */\n if (typeof this.args.data === \"function\") {\n return createStore<Data>(this, this.args.data);\n }\n\n /**\n * Non-instantiable value\n */\n return this.args.data;\n }\n\n element: HTMLDivElement;\n\n constructor(\n owner: Owner,\n args: {\n data: Data | (() => Data) | Newable<Data>;\n key?: string;\n },\n ) {\n super(owner, args);\n\n const element = document.createElement(\"div\");\n\n element.style.display = \"contents\";\n\n const key = this.args.key ?? this.args.data;\n\n LOOKUP.set(element, [key, () => this.data]);\n this.element = element;\n }\n\n <template>\n {{this.element}}\n\n {{#in-element this.element}}\n {{yield}}\n {{/in-element}}\n </template>\n}\n\nfunction findForKey<Data>(startElement: Element, key: string | object): undefined | (() => Data) {\n let parent: Element | null = startElement;\n\n while ((parent = parent.parentElement)) {\n const maybe = LOOKUP.get(parent);\n\n if (!maybe) {\n continue;\n }\n\n if (maybe[0] === key) {\n return maybe[1] as () => Data;\n }\n }\n}\n\ntype DataForKey<Key> = Key extends string\n ? unknown\n : Key extends Newable<infer T>\n ? T\n : Key extends () => infer T\n ? T\n : Key;\n\nexport class Consume<Key extends object | string> extends Component<{\n Args: {\n key: Key;\n };\n Blocks: {\n default: [\n context: {\n data: DataForKey<Key>;\n },\n ];\n };\n}> {\n // SAFETY: We do a runtime assert in the getter below.\n @tracked getData!: () => DataForKey<Key>;\n\n element: HTMLDivElement;\n\n constructor(owner: Owner, args: { key: Key }) {\n super(owner, args);\n\n this.element = document.createElement(\"div\");\n this.element.style.display = \"contents\";\n }\n\n @cached\n get context() {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const self = this;\n\n return {\n get data(): DataForKey<Key> {\n const getData = findForKey<Key>(self.element, self.args.key);\n\n assert(\n `Could not find provided context in <Consume>. Please assure that there is a corresponding <Provide> component before using this <Consume> component`,\n getData,\n );\n\n // SAFETY: return type handled by getter's signature\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return getData() as any;\n },\n };\n }\n\n <template>\n {{this.element}}\n\n {{#in-element this.element}}\n {{yield this.context}}\n {{/in-element}}\n </template>\n}\n"],"names":["LOOKUP","WeakMap","Provide","Component","data","assert","args","createStore","element","constructor","owner","document","createElement","style","display","key","set","setComponentTemplate","precompileTemplate","strictMode","findForKey","startElement","parent","parentElement","maybe","get","Consume","tracked","i","void 0","context","self","getData","n","prototype","cached"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ function isString(x) {
2
+ return typeof x === 'string';
3
+ }
4
+ function isElement(x) {
5
+ return x instanceof Element;
6
+ }
7
+
8
+ export { isElement, isString };
9
+ //# sourceMappingURL=narrowing.js.map
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-primitives",
3
- "version": "0.38.0",
3
+ "version": "0.40.0",
4
4
  "description": "Making apps easier to build",
5
5
  "sideEffects": [
6
6
  "*.css"