1+ /* eslint-disable @typescript-eslint/no-redundant-type-constituents */
2+ import { assert } from " @ember/debug" ;
3+
14import { modifier } from " ember-modifier" ;
25import { cell } from " ember-resources" ;
36
@@ -13,9 +16,17 @@ export interface Signature {
1316 * The name of the PortalTarget to render in to.
1417 * This is the value of the `data-portal-name` attribute
1518 * of the element you wish to render in to.
19+ *
20+ * This can also be an Element which pairs nicely with query-utilities such as `wormhole`, or the platform-native `querySelector`
21+ */
22+ to: (Targets | (string & {})) | Element ;
23+
24+ /**
25+ * Set to true to append to the portal instead of replace
26+ *
27+ * Default: false
1628 */
17- // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
18- to: Targets | (string & {});
29+ append? : boolean ;
1930 };
2031 Blocks: {
2132 /**
@@ -25,6 +36,39 @@ export interface Signature {
2536 };
2637}
2738
39+ /**
40+ * Polyfill for ember-wormhole behavior
41+ *
42+ * Example usage:
43+ * ```gjs
44+ * import { wormhole, Portal } from 'ember-primitives/components/portal';
45+ *
46+ * <template >
47+ * <div id =" the-portal" ></div >
48+ *
49+ * <Portal @ to ={{wormhole " the-portal" }} >
50+ * content renders in the above div
51+ * </Portal >
52+ * </template >
53+ *
54+ * ```
55+ */
56+ export function wormhole(query : string | null | undefined | Element ) {
57+ assert (` Expected query/element to be truthy. ` , query );
58+
59+ if (query instanceof Element ) {
60+ return query ;
61+ }
62+
63+ let found = document .getElementById (query );
64+
65+ found ?? = document .querySelector (query );
66+
67+ assert (` Could not find element with id/selector ${query } ` , found );
68+
69+ return found ;
70+ }
71+
2872const anchor = modifier (
2973 (element : Element , [to , update ]: [string , ReturnType <typeof ElementValue >[" set" ]]) => {
3074 // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
@@ -37,21 +81,43 @@ const anchor = modifier(
3781
3882const ElementValue = () => cell <Element | ShadowRoot >();
3983
84+ function isElement(x : unknown ): x is Element {
85+ return x instanceof Element ;
86+ }
87+
4088export const Portal: TOC <Signature > = <template >
41- {{#let ( ElementValue ) as | target | }}
42- {{! This div is always going to be empty,
89+ {{#if ( isElement @ to) }}
90+ {{#if @ append }}
91+ {{#in-element @ to insertBefore =null }}
92+ {{ yield }}
93+ {{/in-element }}
94+ {{else }}
95+ {{#in-element @ to }}
96+ {{ yield }}
97+ {{/in-element }}
98+ {{/if }}
99+ {{else }}
100+ {{#let ( ElementValue ) as | target | }}
101+ {{! This div is always going to be empty,
43102 because it'll either find the portal and render content elsewhere,
44103 it it won't find the portal and won't render anything.
45- }}
46- {{! template-lint-disable no-inline-styles }}
47- <div style =" display:contents;" {{anchor @ to target.set }} >
48- {{#if target.current }}
49- {{#in-element target.current }}
50- {{ yield }}
51- {{/in-element }}
52- {{/if }}
53- </div >
54- {{/let }}
104+ }}
105+ {{! template-lint-disable no-inline-styles }}
106+ <div style =" display:contents;" {{anchor @ to target.set }} >
107+ {{#if target.current }}
108+ {{#if @ append }}
109+ {{#in-element target.current insertBefore =null }}
110+ {{ yield }}
111+ {{/in-element }}
112+ {{else }}
113+ {{#in-element target.current }}
114+ {{ yield }}
115+ {{/in-element }}
116+ {{/if }}
117+ {{/if }}
118+ </div >
119+ {{/let }}
120+ {{/if }}
55121</template >;
56122
57123export default Portal ;
0 commit comments