ember-primitives 0.49.0 → 0.50.1

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.
Files changed (107) hide show
  1. package/bin/index.mjs +271 -0
  2. package/declarations/color-scheme.d.ts +1 -1
  3. package/declarations/color-scheme.d.ts.map +1 -1
  4. package/declarations/components/rating/public-types.d.ts +0 -4
  5. package/declarations/components/rating/public-types.d.ts.map +1 -1
  6. package/declarations/components/rating/rating.d.ts +9 -1
  7. package/declarations/components/rating/rating.d.ts.map +1 -1
  8. package/declarations/components/rating/stars.d.ts.map +1 -1
  9. package/declarations/components/rating/state.d.ts +4 -0
  10. package/declarations/components/rating/state.d.ts.map +1 -1
  11. package/declarations/components/rating/utils.d.ts +0 -1
  12. package/declarations/components/rating/utils.d.ts.map +1 -1
  13. package/dist/color-scheme.js +13 -4
  14. package/dist/color-scheme.js.map +1 -1
  15. package/dist/components/rating.js +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/{rating-CjBVsX6q.js → rating-BrIiwDLw.js} +21 -17
  18. package/dist/rating-BrIiwDLw.js.map +1 -0
  19. package/package.json +6 -2
  20. package/src/-private.ts +4 -0
  21. package/src/color-scheme.ts +177 -0
  22. package/src/components/-private/typed-elements.gts +13 -0
  23. package/src/components/-private/utils.ts +16 -0
  24. package/src/components/accordion/content.gts +34 -0
  25. package/src/components/accordion/header.gts +36 -0
  26. package/src/components/accordion/item.gts +55 -0
  27. package/src/components/accordion/public.ts +64 -0
  28. package/src/components/accordion/trigger.gts +32 -0
  29. package/src/components/accordion.gts +195 -0
  30. package/src/components/avatar.gts +108 -0
  31. package/src/components/dialog.gts +234 -0
  32. package/src/components/external-link.gts +14 -0
  33. package/src/components/form.gts +75 -0
  34. package/src/components/heading.gts +36 -0
  35. package/src/components/keys.gts +53 -0
  36. package/src/components/layout/hero.css +5 -0
  37. package/src/components/layout/hero.gts +17 -0
  38. package/src/components/layout/sticky-footer.css +9 -0
  39. package/src/components/layout/sticky-footer.gts +40 -0
  40. package/src/components/link.gts +172 -0
  41. package/src/components/menu.gts +373 -0
  42. package/src/components/one-time-password/buttons.gts +31 -0
  43. package/src/components/one-time-password/input.gts +198 -0
  44. package/src/components/one-time-password/otp.gts +130 -0
  45. package/src/components/one-time-password/utils.ts +201 -0
  46. package/src/components/one-time-password.gts +2 -0
  47. package/src/components/popover.gts +248 -0
  48. package/src/components/portal-targets.gts +136 -0
  49. package/src/components/portal.gts +194 -0
  50. package/src/components/progress.gts +154 -0
  51. package/src/components/rating/public-types.ts +44 -0
  52. package/src/components/rating/range.gts +22 -0
  53. package/src/components/rating/rating.gts +228 -0
  54. package/src/components/rating/stars.gts +60 -0
  55. package/src/components/rating/state.gts +144 -0
  56. package/src/components/rating/utils.ts +7 -0
  57. package/src/components/rating.gts +5 -0
  58. package/src/components/scroller.gts +179 -0
  59. package/src/components/shadowed.gts +110 -0
  60. package/src/components/switch.gts +103 -0
  61. package/src/components/tabs.gts +519 -0
  62. package/src/components/toggle-group.gts +265 -0
  63. package/src/components/toggle.gts +81 -0
  64. package/src/components/violations.css +105 -0
  65. package/src/components/violations.css.ts +1 -0
  66. package/src/components/visually-hidden.css +14 -0
  67. package/src/components/visually-hidden.gts +15 -0
  68. package/src/components/zoetrope/index.gts +358 -0
  69. package/src/components/zoetrope/styles.css +40 -0
  70. package/src/components/zoetrope/types.ts +65 -0
  71. package/src/components/zoetrope.ts +3 -0
  72. package/src/dom-context.gts +245 -0
  73. package/src/floating-ui/component.gts +186 -0
  74. package/src/floating-ui/middleware.ts +13 -0
  75. package/src/floating-ui/modifier.ts +183 -0
  76. package/src/floating-ui.ts +2 -0
  77. package/src/head.gts +37 -0
  78. package/src/helpers/body-class.ts +94 -0
  79. package/src/helpers/link.ts +125 -0
  80. package/src/helpers/service.ts +25 -0
  81. package/src/helpers.ts +2 -0
  82. package/src/iframe.ts +31 -0
  83. package/src/index.ts +43 -0
  84. package/src/load.gts +77 -0
  85. package/src/narrowing.ts +7 -0
  86. package/src/on-resize.ts +64 -0
  87. package/src/proper-links.ts +140 -0
  88. package/src/qp.ts +107 -0
  89. package/src/resize-observer.ts +132 -0
  90. package/src/service.ts +103 -0
  91. package/src/store.ts +72 -0
  92. package/src/styles.css.ts +5 -0
  93. package/src/tabster.ts +54 -0
  94. package/src/template-registry.ts +44 -0
  95. package/src/test-support/a11y.ts +50 -0
  96. package/src/test-support/dom.ts +112 -0
  97. package/src/test-support/otp.ts +64 -0
  98. package/src/test-support/rating.ts +144 -0
  99. package/src/test-support/routing.ts +62 -0
  100. package/src/test-support/zoetrope.ts +51 -0
  101. package/src/test-support.gts +6 -0
  102. package/src/type-utils.ts +1 -0
  103. package/src/utils.ts +75 -0
  104. package/src/viewport/in-viewport.gts +128 -0
  105. package/src/viewport/viewport.ts +122 -0
  106. package/src/viewport.ts +2 -0
  107. package/dist/rating-CjBVsX6q.js.map +0 -1
@@ -0,0 +1,248 @@
1
+ import { hash } from "@ember/helper";
2
+
3
+ import { arrow } from "@floating-ui/dom";
4
+ import { element } from "ember-element-helper";
5
+ import { modifier as eModifier } from "ember-modifier";
6
+ import { cell } from "ember-resources";
7
+
8
+ import { FloatingUI } from "../floating-ui.ts";
9
+ import { Portal } from "./portal.gts";
10
+ import { TARGETS } from "./portal-targets.gts";
11
+
12
+ import type { Signature as FloatingUiComponentSignature } from "../floating-ui/component.ts";
13
+ import type { Signature as HookSignature } from "../floating-ui/modifier.ts";
14
+ import type { TOC } from "@ember/component/template-only";
15
+ import type { ElementContext, Middleware } from "@floating-ui/dom";
16
+ import type { ModifierLike, WithBoundArgs } from "@glint/template";
17
+
18
+ export interface Signature {
19
+ Args: {
20
+ /**
21
+ * See the Floating UI's [flip docs](https://floating-ui.com/docs/flip) for possible values.
22
+ *
23
+ * This argument is forwarded to the `<FloatingUI>` component.
24
+ */
25
+ flipOptions?: HookSignature["Args"]["Named"]["flipOptions"];
26
+ /**
27
+ * Array of one or more objects to add to Floating UI's list of [middleware](https://floating-ui.com/docs/middleware)
28
+ *
29
+ * This argument is forwarded to the `<FloatingUI>` component.
30
+ */
31
+ middleware?: HookSignature["Args"]["Named"]["middleware"];
32
+ /**
33
+ * See the Floating UI's [offset docs](https://floating-ui.com/docs/offset) for possible values.
34
+ *
35
+ * This argument is forwarded to the `<FloatingUI>` component.
36
+ */
37
+ offsetOptions?: HookSignature["Args"]["Named"]["offsetOptions"];
38
+ /**
39
+ * One of the possible [`placements`](https://floating-ui.com/docs/computeposition#placement). The default is 'bottom'.
40
+ *
41
+ * Possible values are
42
+ * - top
43
+ * - bottom
44
+ * - right
45
+ * - left
46
+ *
47
+ * And may optionally have `-start` or `-end` added to adjust position along the side.
48
+ *
49
+ * This argument is forwarded to the `<FloatingUI>` component.
50
+ */
51
+ placement?: `${"top" | "bottom" | "left" | "right"}${"" | "-start" | "-end"}`;
52
+ /**
53
+ * See the Floating UI's [shift docs](https://floating-ui.com/docs/shift) for possible values.
54
+ *
55
+ * This argument is forwarded to the `<FloatingUI>` component.
56
+ */
57
+ shiftOptions?: HookSignature["Args"]["Named"]["shiftOptions"];
58
+ /**
59
+ * CSS position property, either `fixed` or `absolute`.
60
+ *
61
+ * Pros and cons of each strategy are explained on [Floating UI's Docs](https://floating-ui.com/docs/computePosition#strategy)
62
+ *
63
+ * This argument is forwarded to the `<FloatingUI>` component.
64
+ */
65
+ strategy?: HookSignature["Args"]["Named"]["strategy"];
66
+
67
+ /**
68
+ * By default, the popover is portaled.
69
+ * If you don't control your CSS, and the positioning of the popover content
70
+ * is misbehaving, you may pass "@inline={{true}}" to opt out of portalling.
71
+ *
72
+ * Inline may also be useful in nested menus, where you know exactly how the nesting occurs
73
+ */
74
+ inline?: boolean;
75
+ };
76
+ Blocks: {
77
+ default: [
78
+ {
79
+ reference: FloatingUiComponentSignature["Blocks"]["default"][0];
80
+ setReference: FloatingUiComponentSignature["Blocks"]["default"][2]["setReference"];
81
+ Content: WithBoundArgs<typeof Content, "floating">;
82
+ data: FloatingUiComponentSignature["Blocks"]["default"][2]["data"];
83
+ arrow: ModifierLike<{ Element: HTMLElement }>;
84
+ },
85
+ ];
86
+ };
87
+ }
88
+
89
+ function getElementTag(tagName: undefined | string) {
90
+ return tagName || "div";
91
+ }
92
+
93
+ /**
94
+ * Allows lazy evaluation of the portal target (do nothing until rendered)
95
+ * This is useful because the algorithm for finding the portal target isn't cheap.
96
+ */
97
+ const Content: TOC<{
98
+ Element: HTMLDivElement;
99
+ Args: {
100
+ floating: ModifierLike<{ Element: HTMLElement }>;
101
+ inline?: boolean;
102
+ /**
103
+ * By default the popover content is wrapped in a div.
104
+ * You may change this by supplying the name of an element here.
105
+ *
106
+ * For example:
107
+ * ```gjs
108
+ * <Popover as |p|>
109
+ * <p.Content @as="dialog">
110
+ * this is now focus trapped
111
+ * </p.Content>
112
+ * </Popover>
113
+ * ```
114
+ */
115
+ as?: string;
116
+ };
117
+ Blocks: { default: [] };
118
+ }> = <template>
119
+ {{#let (element (getElementTag @as)) as |El|}}
120
+ {{#if @inline}}
121
+ {{! @glint-ignore
122
+ https://github.com/tildeio/ember-element-helper/issues/91
123
+ https://github.com/typed-ember/glint/issues/610
124
+ }}
125
+ <El {{@floating}} ...attributes>
126
+ {{yield}}
127
+ </El>
128
+ {{else}}
129
+ <Portal @to={{TARGETS.popover}}>
130
+ {{! @glint-ignore
131
+ https://github.com/tildeio/ember-element-helper/issues/91
132
+ https://github.com/typed-ember/glint/issues/610
133
+ }}
134
+ <El {{@floating}} ...attributes>
135
+ {{yield}}
136
+ </El>
137
+ </Portal>
138
+ {{/if}}
139
+ {{/let}}
140
+ </template>;
141
+
142
+ interface AttachArrowSignature {
143
+ Element: HTMLElement;
144
+ Args: {
145
+ Named: {
146
+ arrowElement: ReturnType<typeof ArrowElement>;
147
+ data:
148
+ | undefined
149
+ | {
150
+ placement: string;
151
+ middlewareData?: {
152
+ arrow?: { x?: number; y?: number };
153
+ };
154
+ };
155
+ };
156
+ };
157
+ }
158
+
159
+ const arrowSides = {
160
+ top: "bottom",
161
+ right: "left",
162
+ bottom: "top",
163
+ left: "right",
164
+ };
165
+
166
+ type Direction = "top" | "bottom" | "left" | "right";
167
+ type Placement = `${Direction}${"" | "-start" | "-end"}`;
168
+
169
+ const attachArrow: ModifierLike<AttachArrowSignature> = eModifier<AttachArrowSignature>(
170
+ (element, _: [], named) => {
171
+ if (element === named.arrowElement.current) {
172
+ if (!named.data) return;
173
+ if (!named.data.middlewareData) return;
174
+
175
+ const { arrow } = named.data.middlewareData;
176
+ const { placement } = named.data;
177
+
178
+ if (!arrow) return;
179
+ if (!placement) return;
180
+
181
+ const { x: arrowX, y: arrowY } = arrow;
182
+ const otherSide = (placement as Placement).split("-")[0] as Direction;
183
+ const staticSide = arrowSides[otherSide];
184
+
185
+ Object.assign(named.arrowElement.current.style, {
186
+ left: arrowX != null ? `${arrowX}px` : "",
187
+ top: arrowY != null ? `${arrowY}px` : "",
188
+ right: "",
189
+ bottom: "",
190
+ [staticSide]: "-4px",
191
+ });
192
+
193
+ return;
194
+ }
195
+
196
+ void (async () => {
197
+ await Promise.resolve();
198
+ named.arrowElement.set(element);
199
+ })();
200
+ },
201
+ );
202
+
203
+ const ArrowElement: () => ReturnType<typeof cell<HTMLElement>> = () => cell<HTMLElement>();
204
+
205
+ function maybeAddArrow(middleware: Middleware[] | undefined, element: Element | undefined) {
206
+ const result = [...(middleware || [])];
207
+
208
+ if (element) {
209
+ result.push(arrow({ element }));
210
+ }
211
+
212
+ return result;
213
+ }
214
+
215
+ function flipOptions(options: HookSignature["Args"]["Named"]["flipOptions"]) {
216
+ return {
217
+ elementContext: "reference" as ElementContext,
218
+ ...options,
219
+ };
220
+ }
221
+
222
+ export const Popover: TOC<Signature> = <template>
223
+ {{#let (ArrowElement) as |arrowElement|}}
224
+ <FloatingUI
225
+ @placement={{@placement}}
226
+ @strategy={{@strategy}}
227
+ @middleware={{maybeAddArrow @middleware arrowElement.current}}
228
+ @flipOptions={{flipOptions @flipOptions}}
229
+ @shiftOptions={{@shiftOptions}}
230
+ @offsetOptions={{@offsetOptions}}
231
+ as |reference floating extra|
232
+ >
233
+ {{#let (modifier attachArrow arrowElement=arrowElement data=extra.data) as |arrow|}}
234
+ {{yield
235
+ (hash
236
+ reference=reference
237
+ setReference=extra.setReference
238
+ Content=(component Content floating=floating inline=@inline)
239
+ data=extra.data
240
+ arrow=arrow
241
+ )
242
+ }}
243
+ {{/let}}
244
+ </FloatingUI>
245
+ {{/let}}
246
+ </template>;
247
+
248
+ export default Popover;
@@ -0,0 +1,136 @@
1
+ import { assert } from "@ember/debug";
2
+ import { isDevelopingApp, macroCondition } from "@embroider/macros";
3
+
4
+ import { modifier } from "ember-modifier";
5
+ import { TrackedMap, TrackedSet } from "tracked-built-ins";
6
+
7
+ import type { TOC } from "@ember/component/template-only";
8
+
9
+ const cache = new TrackedMap<string, Set<Element>>();
10
+
11
+ export const TARGETS = Object.freeze({
12
+ popover: "ember-primitives__portal-targets__popover",
13
+ tooltip: "ember-primitives__portal-targets__tooltip",
14
+ modal: "ember-primitives__portal-targets__modal",
15
+ });
16
+
17
+ export function findNearestTarget(origin: Element, name: string): Element | undefined {
18
+ assert(`first argument to \`findNearestTarget\` must be an element`, origin instanceof Element);
19
+ assert(`second argument to \`findNearestTarget\` must be a string`, typeof name === `string`);
20
+
21
+ let element: Element | undefined | null = null;
22
+
23
+ let parent = origin.parentNode;
24
+
25
+ const manuallyRegisteredSet = cache.get(name);
26
+ const manuallyRegistered: Element[] | null = manuallyRegisteredSet?.size
27
+ ? [...manuallyRegisteredSet]
28
+ : null;
29
+
30
+ /**
31
+ * For use with <PortalTarget @name="hi" />
32
+ */
33
+ function findRegistered(host: ParentNode): Element | undefined {
34
+ return manuallyRegistered?.find((element) => {
35
+ if (host.contains(element)) {
36
+ return element;
37
+ }
38
+ });
39
+ }
40
+
41
+ const selector = Object.values(TARGETS as Record<string, string>).includes(name)
42
+ ? `[data-portal-name=${name}]`
43
+ : name;
44
+
45
+ /**
46
+ * Default portals / non-registered -- here we match a query selector instead of an element
47
+ */
48
+ function findDefault(host: ParentNode): Element | undefined {
49
+ return host.querySelector(selector) as Element;
50
+ }
51
+
52
+ const finder = manuallyRegistered ? findRegistered : findDefault;
53
+
54
+ /**
55
+ * Crawl up the ancestry looking for our portal target
56
+ */
57
+ while (!element && parent) {
58
+ element = finder(parent);
59
+ if (element) break;
60
+ parent = parent.parentNode;
61
+ }
62
+
63
+ if (macroCondition(isDevelopingApp())) {
64
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
65
+ (window as any).prime0 = origin;
66
+ }
67
+
68
+ if (name.startsWith("ember-primitives")) {
69
+ assert(
70
+ `Could not find element by the given name: \`${name}\`.` +
71
+ ` The known names are ` +
72
+ `${Object.values(TARGETS).join(", ")} ` +
73
+ `-- but any name will work as long as it is set to the \`data-portal-name\` attribute ` +
74
+ `(or if the name has been specifically registered via the <PortalTarget /> component). ` +
75
+ `Double check that the element you're wanting to portal to is rendered. ` +
76
+ `The element passed to \`findNearestTarget\` is stored on \`window.prime0\` ` +
77
+ `You can debug in your browser's console via ` +
78
+ `\`document.querySelector('[data-portal-name="${name}"]')\``,
79
+ element,
80
+ );
81
+ }
82
+
83
+ return element ?? undefined;
84
+ }
85
+
86
+ const register = modifier((element: Element, [name]: [name: string]) => {
87
+ assert(`@name is required when using <PortalTarget>`, name);
88
+
89
+ void (async () => {
90
+ // Bad TypeScript lint.
91
+ // eslint-disable-next-line @typescript-eslint/await-thenable
92
+ await 0;
93
+
94
+ let existing = cache.get(name);
95
+
96
+ if (!existing) {
97
+ existing = new TrackedSet<Element>();
98
+ cache.set(name, existing);
99
+ }
100
+
101
+ existing.add(element);
102
+ })();
103
+
104
+ return () => {
105
+ cache.delete(name);
106
+ };
107
+ });
108
+
109
+ export interface Signature {
110
+ Element: null;
111
+ }
112
+
113
+ export const PortalTargets: TOC<Signature> = <template>
114
+ <div data-portal-name={{TARGETS.popover}}></div>
115
+ <div data-portal-name={{TARGETS.tooltip}}></div>
116
+ <div data-portal-name={{TARGETS.modal}}></div>
117
+ </template>;
118
+
119
+ /**
120
+ * For manually registering a PortalTarget for use with Portal
121
+ */
122
+ export const PortalTarget: TOC<{
123
+ Element: HTMLDivElement;
124
+ Args: {
125
+ /**
126
+ * The name of the PortalTarget
127
+ *
128
+ * This exact string may be passed to `Portal`'s `@to` argument.
129
+ */
130
+ name: string;
131
+ };
132
+ }> = <template>
133
+ <div {{register @name}} ...attributes></div>
134
+ </template>;
135
+
136
+ export default PortalTargets;
@@ -0,0 +1,194 @@
1
+ import { assert } from "@ember/debug";
2
+ import { schedule } from "@ember/runloop";
3
+ import { buildWaiter } from "@ember/test-waiters";
4
+
5
+ import { modifier } from "ember-modifier";
6
+ import { cell, resource, resourceFactory } from "ember-resources";
7
+
8
+ import { isElement } from "../narrowing.ts";
9
+ import { findNearestTarget, type TARGETS } from "./portal-targets.gts";
10
+
11
+ import type { TOC } from "@ember/component/template-only";
12
+
13
+ type Targets = (typeof TARGETS)[keyof typeof TARGETS];
14
+
15
+ interface ToSignature {
16
+ Args: {
17
+ to: string;
18
+ append?: boolean;
19
+ };
20
+ Blocks: {
21
+ default: [];
22
+ };
23
+ }
24
+ interface ElementSignature {
25
+ Args: {
26
+ to: Element;
27
+ append?: boolean;
28
+ };
29
+ Blocks: {
30
+ default: [];
31
+ };
32
+ }
33
+
34
+ export interface Signature {
35
+ Args: {
36
+ /**
37
+ * The name of the PortalTarget to render in to.
38
+ * This is the value of the `data-portal-name` attribute
39
+ * of the element you wish to render in to.
40
+ *
41
+ * This can also be an Element which pairs nicely with query-utilities such as the platform-native `querySelector`
42
+ */
43
+ to?: (Targets | (string & {})) | Element;
44
+
45
+ /**
46
+ * Set to true to append to the portal instead of replace
47
+ *
48
+ * Default: false
49
+ */
50
+ append?: boolean;
51
+ /**
52
+ * For ember-wormhole style behavior, this argument may be an id,
53
+ * or a selector.
54
+ * This can also be an element, in which case the behavior is identical to `@to`
55
+ */
56
+ wormhole?: string | Element;
57
+ };
58
+ Blocks: {
59
+ /**
60
+ * The portaled content
61
+ */
62
+ default: [];
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Polyfill for ember-wormhole behavior
68
+ *
69
+ * Example usage:
70
+ * ```gjs
71
+ * import { wormhole, Portal } from 'ember-primitives/components/portal';
72
+ *
73
+ * <template>
74
+ * <div id="the-portal"></div>
75
+ *
76
+ * <Portal @to={{wormhole "the-portal"}}>
77
+ * content renders in the above div
78
+ * </Portal>
79
+ * </template>
80
+ *
81
+ * ```
82
+ */
83
+ export function wormhole(query: string | null | undefined | Element) {
84
+ assert(`Expected query/element to be truthy.`, query);
85
+
86
+ if (isElement(query)) {
87
+ return query;
88
+ }
89
+
90
+ let found = document.getElementById(query);
91
+
92
+ found ??= document.querySelector(query);
93
+
94
+ return found;
95
+ }
96
+
97
+ const anchor = modifier(
98
+ (element: Element, [to, update]: [string, ReturnType<typeof ElementValue>["set"]]) => {
99
+ const found = findNearestTarget(element, to);
100
+
101
+ update(found);
102
+ },
103
+ );
104
+
105
+ const ElementValue = () => cell<Element | ShadowRoot | null | undefined>();
106
+
107
+ const waiter = buildWaiter("ember-primitives:portal");
108
+
109
+ function wormholeCompat(selector: string | Element) {
110
+ const target = wormhole(selector);
111
+
112
+ if (target) return target;
113
+
114
+ return resource(() => {
115
+ const target = cell<Element | undefined | null>();
116
+
117
+ const token = waiter.beginAsync();
118
+
119
+ // eslint-disable-next-line ember/no-runloop
120
+ schedule("afterRender", () => {
121
+ const result = wormhole(selector);
122
+
123
+ waiter.endAsync(token);
124
+ target.current = result;
125
+ assert(
126
+ `Could not find element with id/selector \`${typeof selector === "string" ? selector : "<Element>"}\``,
127
+ result,
128
+ );
129
+ });
130
+
131
+ return () => target.current;
132
+ });
133
+ }
134
+
135
+ resourceFactory(wormholeCompat);
136
+
137
+ export const Portal: TOC<Signature> = <template>
138
+ {{#if (isElement @to)}}
139
+ <ToElement @to={{@to}} @append={{@append}}>
140
+ {{yield}}
141
+ </ToElement>
142
+ {{else if @wormhole}}
143
+ {{#let (wormholeCompat @wormhole) as |target|}}
144
+ {{#if target}}
145
+ {{#in-element target insertBefore=null}}
146
+ {{yield}}
147
+ {{/in-element}}
148
+ {{/if}}
149
+ {{/let}}
150
+ {{else if @to}}
151
+ <Nestable @to={{@to}} @append={{@append}}>
152
+ {{yield}}
153
+ </Nestable>
154
+ {{else}}
155
+ {{assert "either @to or @wormhole is required. Received neither"}}
156
+ {{/if}}
157
+ </template>;
158
+
159
+ const ToElement: TOC<ElementSignature> = <template>
160
+ {{#if @append}}
161
+ {{#in-element @to insertBefore=null}}
162
+ {{yield}}
163
+ {{/in-element}}
164
+ {{else}}
165
+ {{#in-element @to}}
166
+ {{yield}}
167
+ {{/in-element}}
168
+ {{/if}}
169
+ </template>;
170
+
171
+ const Nestable: TOC<ToSignature> = <template>
172
+ {{#let (ElementValue) as |target|}}
173
+ {{! This div is always going to be empty,
174
+ because it'll either find the portal and render content elsewhere,
175
+ it it won't find the portal and won't render anything.
176
+ }}
177
+ {{! template-lint-disable no-inline-styles }}
178
+ <div style="display:contents;" {{anchor @to target.set}}>
179
+ {{#if target.current}}
180
+ {{#if @append}}
181
+ {{#in-element target.current insertBefore=null}}
182
+ {{yield}}
183
+ {{/in-element}}
184
+ {{else}}
185
+ {{#in-element target.current}}
186
+ {{yield}}
187
+ {{/in-element}}
188
+ {{/if}}
189
+ {{/if}}
190
+ </div>
191
+ {{/let}}
192
+ </template>;
193
+
194
+ export default Portal;