ember-primitives 0.49.0 → 0.50.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.
Files changed (103) hide show
  1. package/bin/index.mjs +271 -0
  2. package/declarations/components/rating/public-types.d.ts +0 -4
  3. package/declarations/components/rating/public-types.d.ts.map +1 -1
  4. package/declarations/components/rating/rating.d.ts +9 -1
  5. package/declarations/components/rating/rating.d.ts.map +1 -1
  6. package/declarations/components/rating/stars.d.ts.map +1 -1
  7. package/declarations/components/rating/state.d.ts +4 -0
  8. package/declarations/components/rating/state.d.ts.map +1 -1
  9. package/declarations/components/rating/utils.d.ts +0 -1
  10. package/declarations/components/rating/utils.d.ts.map +1 -1
  11. package/dist/components/rating.js +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/{rating-CjBVsX6q.js → rating-BrIiwDLw.js} +21 -17
  14. package/dist/rating-BrIiwDLw.js.map +1 -0
  15. package/package.json +6 -2
  16. package/src/-private.ts +4 -0
  17. package/src/color-scheme.ts +165 -0
  18. package/src/components/-private/typed-elements.gts +13 -0
  19. package/src/components/-private/utils.ts +16 -0
  20. package/src/components/accordion/content.gts +34 -0
  21. package/src/components/accordion/header.gts +36 -0
  22. package/src/components/accordion/item.gts +55 -0
  23. package/src/components/accordion/public.ts +64 -0
  24. package/src/components/accordion/trigger.gts +32 -0
  25. package/src/components/accordion.gts +195 -0
  26. package/src/components/avatar.gts +108 -0
  27. package/src/components/dialog.gts +234 -0
  28. package/src/components/external-link.gts +14 -0
  29. package/src/components/form.gts +75 -0
  30. package/src/components/heading.gts +36 -0
  31. package/src/components/keys.gts +53 -0
  32. package/src/components/layout/hero.css +5 -0
  33. package/src/components/layout/hero.gts +17 -0
  34. package/src/components/layout/sticky-footer.css +9 -0
  35. package/src/components/layout/sticky-footer.gts +40 -0
  36. package/src/components/link.gts +172 -0
  37. package/src/components/menu.gts +373 -0
  38. package/src/components/one-time-password/buttons.gts +31 -0
  39. package/src/components/one-time-password/input.gts +198 -0
  40. package/src/components/one-time-password/otp.gts +130 -0
  41. package/src/components/one-time-password/utils.ts +201 -0
  42. package/src/components/one-time-password.gts +2 -0
  43. package/src/components/popover.gts +248 -0
  44. package/src/components/portal-targets.gts +136 -0
  45. package/src/components/portal.gts +194 -0
  46. package/src/components/progress.gts +154 -0
  47. package/src/components/rating/public-types.ts +44 -0
  48. package/src/components/rating/range.gts +22 -0
  49. package/src/components/rating/rating.gts +228 -0
  50. package/src/components/rating/stars.gts +60 -0
  51. package/src/components/rating/state.gts +144 -0
  52. package/src/components/rating/utils.ts +7 -0
  53. package/src/components/rating.gts +5 -0
  54. package/src/components/scroller.gts +179 -0
  55. package/src/components/shadowed.gts +110 -0
  56. package/src/components/switch.gts +103 -0
  57. package/src/components/tabs.gts +519 -0
  58. package/src/components/toggle-group.gts +265 -0
  59. package/src/components/toggle.gts +81 -0
  60. package/src/components/violations.css +105 -0
  61. package/src/components/violations.css.ts +1 -0
  62. package/src/components/visually-hidden.css +14 -0
  63. package/src/components/visually-hidden.gts +15 -0
  64. package/src/components/zoetrope/index.gts +358 -0
  65. package/src/components/zoetrope/styles.css +40 -0
  66. package/src/components/zoetrope/types.ts +65 -0
  67. package/src/components/zoetrope.ts +3 -0
  68. package/src/dom-context.gts +245 -0
  69. package/src/floating-ui/component.gts +186 -0
  70. package/src/floating-ui/middleware.ts +13 -0
  71. package/src/floating-ui/modifier.ts +183 -0
  72. package/src/floating-ui.ts +2 -0
  73. package/src/head.gts +37 -0
  74. package/src/helpers/body-class.ts +94 -0
  75. package/src/helpers/link.ts +125 -0
  76. package/src/helpers/service.ts +25 -0
  77. package/src/helpers.ts +2 -0
  78. package/src/iframe.ts +31 -0
  79. package/src/index.ts +43 -0
  80. package/src/load.gts +77 -0
  81. package/src/narrowing.ts +7 -0
  82. package/src/on-resize.ts +64 -0
  83. package/src/proper-links.ts +140 -0
  84. package/src/qp.ts +107 -0
  85. package/src/resize-observer.ts +132 -0
  86. package/src/service.ts +103 -0
  87. package/src/store.ts +72 -0
  88. package/src/styles.css.ts +5 -0
  89. package/src/tabster.ts +54 -0
  90. package/src/template-registry.ts +44 -0
  91. package/src/test-support/a11y.ts +50 -0
  92. package/src/test-support/dom.ts +112 -0
  93. package/src/test-support/otp.ts +64 -0
  94. package/src/test-support/rating.ts +144 -0
  95. package/src/test-support/routing.ts +62 -0
  96. package/src/test-support/zoetrope.ts +51 -0
  97. package/src/test-support.gts +6 -0
  98. package/src/type-utils.ts +1 -0
  99. package/src/utils.ts +75 -0
  100. package/src/viewport/in-viewport.gts +128 -0
  101. package/src/viewport/viewport.ts +122 -0
  102. package/src/viewport.ts +2 -0
  103. package/dist/rating-CjBVsX6q.js.map +0 -1
@@ -0,0 +1,186 @@
1
+ import Component from "@glimmer/component";
2
+ import { tracked } from "@glimmer/tracking";
3
+ import { hash } from "@ember/helper";
4
+
5
+ import { modifier as eModifier } from "ember-modifier";
6
+
7
+ import { anchorTo } from "./modifier.ts";
8
+
9
+ import type { Signature as ModifierSignature } from "./modifier.ts";
10
+ import type { MiddlewareState } from "@floating-ui/dom";
11
+ import type { ModifierLike } from "@glint/template";
12
+
13
+ type ModifierArgs = ModifierSignature["Args"]["Named"];
14
+
15
+ interface ReferenceSignature {
16
+ Element: HTMLElement | SVGElement;
17
+ }
18
+
19
+ export interface Signature {
20
+ Args: {
21
+ /**
22
+ * Additional middleware to pass to FloatingUI.
23
+ *
24
+ * See: [The middleware docs](https://floating-ui.com/docs/middleware)
25
+ */
26
+ middleware?: ModifierArgs["middleware"];
27
+ /**
28
+ * Where to place the floating element relative to its reference element.
29
+ * The default is 'bottom'.
30
+ *
31
+ * See: [The placement docs](https://floating-ui.com/docs/computePosition#placement)
32
+ */
33
+ placement?: ModifierArgs["placement"];
34
+ /**
35
+ * This is the type of CSS position property to use.
36
+ * By default this is 'fixed', but can also be 'absolute'.
37
+ *
38
+ * See: [The strategy docs](https://floating-ui.com/docs/computePosition#strategy)
39
+ */
40
+ strategy?: ModifierArgs["strategy"];
41
+ /**
42
+ * Options to pass to the [flip middleware](https://floating-ui.com/docs/flip)
43
+ */
44
+ flipOptions?: ModifierArgs["flipOptions"];
45
+ /**
46
+ * Options to pass to the [hide middleware](https://floating-ui.com/docs/hide)
47
+ */
48
+ hideOptions?: ModifierArgs["hideOptions"];
49
+ /**
50
+ * Options to pass to the [shift middleware](https://floating-ui.com/docs/shift)
51
+ */
52
+ shiftOptions?: ModifierArgs["shiftOptions"];
53
+ /**
54
+ * Options to pass to the [offset middleware](https://floating-ui.com/docs/offset)
55
+ */
56
+ offsetOptions?: ModifierArgs["offsetOptions"];
57
+ };
58
+ Blocks: {
59
+ default: [
60
+ /**
61
+ * A modifier to apply to the _reference_ element.
62
+ * This is what the floating element will use to anchor to.
63
+ *
64
+ * Example
65
+ * ```gjs
66
+ * import { FloatingUI } from 'ember-primitives/floating-ui';
67
+ *
68
+ * <template>
69
+ * <FloatingUI as |reference floating|>
70
+ * <button {{reference}}> ... </button>
71
+ * ...
72
+ * </FloatingUI>
73
+ * </template>
74
+ * ```
75
+ */
76
+ reference: ModifierLike<ReferenceSignature>,
77
+ /**
78
+ * A modifier to apply to the _floating_ element.
79
+ * This is what will anchor to the reference element.
80
+ *
81
+ * Example
82
+ * ```gjs
83
+ * import { FloatingUI } from 'ember-primitives/floating-ui';
84
+ *
85
+ * <template>
86
+ * <FloatingUI as |reference floating|>
87
+ * <button {{reference}}> ... </button>
88
+ * <menu {{floating}}> ... </menu>
89
+ * </FloatingUI>
90
+ * </template>
91
+ * ```
92
+ */
93
+ floating:
94
+ | undefined
95
+ | ModifierLike<{
96
+ Element: HTMLElement;
97
+ Args: {
98
+ Named: ModifierArgs;
99
+ };
100
+ }>,
101
+ /**
102
+ * Special utilities for advanced usage
103
+ */
104
+ util: {
105
+ /**
106
+ * If you want to have a single modifier with custom behavior
107
+ * on your reference element, you may use this `setReference`
108
+ * function to set the reference, rather than having multiple modifiers
109
+ * on that element.
110
+ */
111
+ setReference: (element: HTMLElement | SVGElement) => void;
112
+ /**
113
+ * Metadata exposed from floating-ui.
114
+ * Gives you x, y position, among other things.
115
+ */
116
+ data?: MiddlewareState;
117
+ },
118
+ ];
119
+ };
120
+ }
121
+
122
+ const ref = eModifier<{
123
+ Element: HTMLElement | SVGElement;
124
+ Args: {
125
+ Positional: [setRef: (element: HTMLElement | SVGElement) => void];
126
+ };
127
+ }>((element: HTMLElement | SVGElement, positional) => {
128
+ const fn = positional[0];
129
+
130
+ fn(element);
131
+ });
132
+
133
+ /**
134
+ * A component that provides no DOM and yields two modifiers for creating
135
+ * creating floating uis, such as menus, popovers, tooltips, etc.
136
+ * This component currently uses [Floating UI](https://floating-ui.com/)
137
+ * but will be switching to [CSS Anchor Positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning) when that lands.
138
+ *
139
+ * Example usage:
140
+ * ```gjs
141
+ * import { FloatingUI } from 'ember-primitives/floating-ui';
142
+ *
143
+ * <template>
144
+ * <FloatingUI as |reference floating|>
145
+ * <button {{reference}}> ... </button>
146
+ * <menu {{floating}}> ... </menu>
147
+ * </FloatingUI>
148
+ * </template>
149
+ * ```
150
+ */
151
+ export class FloatingUI extends Component<Signature> {
152
+ @tracked reference?: HTMLElement | SVGElement = undefined;
153
+ @tracked data?: MiddlewareState = undefined;
154
+
155
+ setData: ModifierArgs["setData"] = (data) => (this.data = data);
156
+
157
+ setReference = (element: HTMLElement | SVGElement) => {
158
+ this.reference = element;
159
+ };
160
+
161
+ <template>
162
+ {{#let
163
+ (modifier
164
+ anchorTo
165
+ flipOptions=@flipOptions
166
+ hideOptions=@hideOptions
167
+ middleware=@middleware
168
+ offsetOptions=@offsetOptions
169
+ placement=@placement
170
+ shiftOptions=@shiftOptions
171
+ strategy=@strategy
172
+ setData=this.setData
173
+ )
174
+ as |prewiredAnchorTo|
175
+ }}
176
+ {{#let (if this.reference (modifier prewiredAnchorTo this.reference)) as |floating|}}
177
+ {{! @glint-nocheck -- Excessively deep, possibly infinite }}
178
+ {{yield
179
+ (modifier ref this.setReference)
180
+ floating
181
+ (hash setReference=this.setReference data=this.data)
182
+ }}
183
+ {{/let}}
184
+ {{/let}}
185
+ </template>
186
+ }
@@ -0,0 +1,13 @@
1
+ import type { Middleware } from '@floating-ui/dom';
2
+
3
+ export function exposeMetadata(): Middleware {
4
+ return {
5
+ name: 'metadata',
6
+ fn: (data) => {
7
+ // https://floating-ui.com/docs/middleware#always-return-an-object
8
+ return {
9
+ data,
10
+ };
11
+ },
12
+ };
13
+ }
@@ -0,0 +1,183 @@
1
+ import { assert } from '@ember/debug';
2
+
3
+ import { autoUpdate, computePosition, flip, hide, offset, shift } from '@floating-ui/dom';
4
+ import { modifier as eModifier } from 'ember-modifier';
5
+
6
+ import { exposeMetadata } from './middleware.ts';
7
+
8
+ import type {
9
+ FlipOptions,
10
+ HideOptions,
11
+ Middleware,
12
+ OffsetOptions,
13
+ Placement,
14
+ ShiftOptions,
15
+ Strategy,
16
+ } from '@floating-ui/dom';
17
+
18
+ export interface Signature {
19
+ /**
20
+ *
21
+ */
22
+ Element: HTMLElement;
23
+ Args: {
24
+ Positional: [
25
+ /**
26
+ * What do use as the reference element.
27
+ * Can be a selector or element instance.
28
+ *
29
+ * Example:
30
+ * ```gjs
31
+ * import { anchorTo } from 'ember-primitives/floating-ui';
32
+ *
33
+ * <template>
34
+ * <div id="reference">...</div>
35
+ * <div {{anchorTo "#reference"}}> ... </div>
36
+ * </template>
37
+ * ```
38
+ */
39
+ referenceElement: string | HTMLElement | SVGElement,
40
+ ];
41
+ Named: {
42
+ /**
43
+ * This is the type of CSS position property to use.
44
+ * By default this is 'fixed', but can also be 'absolute'.
45
+ *
46
+ * See: [The strategy docs](https://floating-ui.com/docs/computePosition#strategy)
47
+ */
48
+ strategy?: Strategy;
49
+ /**
50
+ * Options to pass to the [offset middleware](https://floating-ui.com/docs/offset)
51
+ */
52
+ offsetOptions?: OffsetOptions;
53
+ /**
54
+ * Where to place the floating element relative to its reference element.
55
+ * The default is 'bottom'.
56
+ *
57
+ * See: [The placement docs](https://floating-ui.com/docs/computePosition#placement)
58
+ */
59
+ placement?: Placement;
60
+ /**
61
+ * Options to pass to the [flip middleware](https://floating-ui.com/docs/flip)
62
+ */
63
+ flipOptions?: FlipOptions;
64
+ /**
65
+ * Options to pass to the [shift middleware](https://floating-ui.com/docs/shift)
66
+ */
67
+ shiftOptions?: ShiftOptions;
68
+ /**
69
+ * Options to pass to the [hide middleware](https://floating-ui.com/docs/hide)
70
+ */
71
+ hideOptions?: HideOptions;
72
+ /**
73
+ * Additional middleware to pass to FloatingUI.
74
+ *
75
+ * See: [The middleware docs](https://floating-ui.com/docs/middleware)
76
+ */
77
+ middleware?: Middleware[];
78
+ /**
79
+ * A callback for when data changes about the position / placement / etc
80
+ * of the floating element.
81
+ */
82
+ setData?: Middleware['fn'];
83
+ };
84
+ };
85
+ }
86
+
87
+ /**
88
+ * A modifier to apply to the _floating_ element.
89
+ * This is what will anchor to the reference element.
90
+ *
91
+ * Example
92
+ * ```gjs
93
+ * import { anchorTo } from 'ember-primitives/floating-ui';
94
+ *
95
+ * <template>
96
+ * <button id="my-button"> ... </button>
97
+ * <menu {{anchorTo "#my-button"}}> ... </menu>
98
+ * </template>
99
+ * ```
100
+ */
101
+ export const anchorTo = eModifier<Signature>(
102
+ (
103
+ floatingElement,
104
+ [_referenceElement],
105
+ {
106
+ strategy = 'fixed',
107
+ offsetOptions = 0,
108
+ placement = 'bottom',
109
+ flipOptions,
110
+ shiftOptions,
111
+ middleware = [],
112
+ setData,
113
+ }
114
+ ) => {
115
+ const referenceElement: null | HTMLElement | SVGElement =
116
+ typeof _referenceElement === 'string'
117
+ ? document.querySelector(_referenceElement)
118
+ : _referenceElement;
119
+
120
+ assert(
121
+ 'no reference element defined',
122
+ referenceElement instanceof HTMLElement || referenceElement instanceof SVGElement
123
+ );
124
+
125
+ assert(
126
+ 'no floating element defined',
127
+ floatingElement instanceof HTMLElement || _referenceElement instanceof SVGElement
128
+ );
129
+
130
+ assert(
131
+ 'reference and floating elements cannot be the same element',
132
+ floatingElement !== _referenceElement
133
+ );
134
+
135
+ assert('@middleware must be an array of one or more objects', Array.isArray(middleware));
136
+
137
+ Object.assign(floatingElement.style, {
138
+ position: strategy,
139
+ top: '0',
140
+ left: '0',
141
+ });
142
+
143
+ const update = async () => {
144
+ const { middlewareData, x, y } = await computePosition(referenceElement, floatingElement, {
145
+ middleware: [
146
+ offset(offsetOptions),
147
+ flip(flipOptions),
148
+ shift(shiftOptions),
149
+ ...middleware,
150
+ hide({ strategy: 'referenceHidden' }),
151
+ hide({ strategy: 'escaped' }),
152
+ exposeMetadata(),
153
+ ],
154
+ placement,
155
+ strategy,
156
+ });
157
+
158
+ const referenceHidden = middlewareData.hide?.referenceHidden;
159
+
160
+ Object.assign(floatingElement.style, {
161
+ top: `${y}px`,
162
+ left: `${x}px`,
163
+ margin: 0,
164
+ visibility: referenceHidden ? 'hidden' : 'visible',
165
+ });
166
+
167
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
168
+ void setData?.(middlewareData['metadata']);
169
+ };
170
+
171
+ void update();
172
+
173
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
174
+ const cleanup = autoUpdate(referenceElement, floatingElement, update);
175
+
176
+ /**
177
+ * in the function-modifier manager, teardown of the previous modifier
178
+ * occurs before setup of the next
179
+ * https://github.com/ember-modifier/ember-modifier/blob/main/ember-modifier/src/-private/function-based/modifier-manager.ts#L58
180
+ */
181
+ return cleanup;
182
+ }
183
+ );
@@ -0,0 +1,2 @@
1
+ export { FloatingUI } from './floating-ui/component.gts';
2
+ export { anchorTo } from './floating-ui/modifier.ts';
package/src/head.gts ADDED
@@ -0,0 +1,37 @@
1
+ import type { TOC } from "@ember/component/template-only";
2
+
3
+ export interface Signature {
4
+ Blocks: {
5
+ /**
6
+ * Content to render in to the `<head>` element
7
+ */
8
+ default: [];
9
+ };
10
+ }
11
+
12
+ function getHead() {
13
+ return document.head;
14
+ }
15
+
16
+ /**
17
+ * Utility component to place elements in the document `<head>`
18
+ *
19
+ * When this component is unrendered, its contents will be removed as well.
20
+ *
21
+ * @example
22
+ * ```js
23
+ * import { InHead } from 'ember-primitives/head';
24
+ *
25
+ * <template>
26
+ * {{#if @useBootstrap}}
27
+ * <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"></script>
28
+ * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css">
29
+ * {{/if}}
30
+ * </template>
31
+ * ```
32
+ */
33
+ export const InHead: TOC<Signature> = <template>
34
+ {{#in-element (getHead) insertBefore=null}}
35
+ {{yield}}
36
+ {{/in-element}}
37
+ </template>;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Initial inspo:
3
+ * - https://github.com/ef4/ember-set-body-class/blob/master/addon/services/body-class.js
4
+ * - https://github.com/ef4/ember-set-body-class/blob/master/addon/helpers/set-body-class.js
5
+ */
6
+ import Helper from '@ember/component/helper';
7
+ import { buildWaiter } from '@ember/test-waiters';
8
+
9
+ const waiter = buildWaiter('ember-primitives:body-class:raf');
10
+
11
+ let id = 0;
12
+ const registrations = new Map<number, string[]>();
13
+ let previousRegistrations: string[] = [];
14
+
15
+ function classNames(): string[] {
16
+ const allNames = new Set<string>();
17
+
18
+ for (const classNames of registrations.values()) {
19
+ for (const className of classNames) {
20
+ allNames.add(className);
21
+ }
22
+ }
23
+
24
+ return [...allNames];
25
+ }
26
+
27
+ let frame: number;
28
+ let waiterToken: unknown;
29
+
30
+ function queueUpdate() {
31
+ waiterToken ||= waiter.beginAsync();
32
+
33
+ cancelAnimationFrame(frame);
34
+ frame = requestAnimationFrame(() => {
35
+ updateBodyClass();
36
+ waiter.endAsync(waiterToken);
37
+ waiterToken = undefined;
38
+ });
39
+ }
40
+
41
+ /**
42
+ * This should only add/remove classes that we tried to maintain via the body-class helper.
43
+ *
44
+ * Folks can set classes in their html and we don't want to mess with those
45
+ */
46
+ function updateBodyClass() {
47
+ const toAdd = classNames();
48
+
49
+ for (const name of previousRegistrations) {
50
+ document.body.classList.remove(name);
51
+ }
52
+
53
+ for (const name of toAdd) {
54
+ document.body.classList.add(name);
55
+ }
56
+
57
+ previousRegistrations = toAdd;
58
+ }
59
+
60
+ export interface Signature {
61
+ Args: {
62
+ Positional: [
63
+ /**
64
+ * a space-delimited list of classes to apply when this helper is called.
65
+ *
66
+ * When the helper is removed from rendering, the clasess will be removed as well.
67
+ */
68
+ classes: string,
69
+ ];
70
+ };
71
+ /**
72
+ * This helper returns nothing, as it is a side-effect that mutates and manages external state.
73
+ */
74
+ Return: undefined;
75
+ }
76
+
77
+ export default class BodyClass extends Helper<Signature> {
78
+ localId = id++;
79
+
80
+ compute([classes]: [string]): undefined {
81
+ const classNames = classes ? classes.split(/\s+/) : [];
82
+
83
+ registrations.set(this.localId, classNames);
84
+
85
+ queueUpdate();
86
+ }
87
+
88
+ willDestroy() {
89
+ registrations.delete(this.localId);
90
+ queueUpdate();
91
+ }
92
+ }
93
+
94
+ export const bodyClass = BodyClass;
@@ -0,0 +1,125 @@
1
+ import Helper from '@ember/component/helper';
2
+ import { assert } from '@ember/debug';
3
+ import { service } from '@ember/service';
4
+
5
+ import { handle } from '../proper-links.ts';
6
+
7
+ import type RouterService from '@ember/routing/router-service';
8
+
9
+ export interface Signature {
10
+ Args: {
11
+ Positional: [href: string];
12
+ Named: {
13
+ includeActiveQueryParams?: boolean | string[];
14
+ activeOnSubPaths?: boolean;
15
+ };
16
+ };
17
+ Return: {
18
+ isExternal: boolean;
19
+ isActive: boolean;
20
+ handleClick: (event: MouseEvent) => void;
21
+ };
22
+ }
23
+
24
+ export default class Link extends Helper<Signature> {
25
+ @service declare router: RouterService;
26
+
27
+ compute(
28
+ [href]: [href: string],
29
+ {
30
+ includeActiveQueryParams = false,
31
+ activeOnSubPaths = false,
32
+ }: { includeActiveQueryParams?: boolean | string[]; activeOnSubPaths?: boolean }
33
+ ) {
34
+ assert('href was not passed in', href);
35
+
36
+ const router = this.router;
37
+ const handleClick = (event: MouseEvent) => {
38
+ assert('[BUG]', event.currentTarget instanceof HTMLAnchorElement);
39
+
40
+ handle(router, event.currentTarget, [], event);
41
+ };
42
+
43
+ return {
44
+ isExternal: isExternal(href),
45
+ get isActive() {
46
+ return isActive(router, href, includeActiveQueryParams, activeOnSubPaths);
47
+ },
48
+ handleClick,
49
+ };
50
+ }
51
+ }
52
+
53
+ export const link = Link;
54
+
55
+ export function isExternal(href: string) {
56
+ if (!href) return false;
57
+ if (href.startsWith('#')) return false;
58
+ if (href.startsWith('/')) return false;
59
+
60
+ return location.origin !== new URL(href).origin;
61
+ }
62
+
63
+ export function isActive(
64
+ router: RouterService,
65
+ href: string,
66
+ includeQueryParams?: boolean | string[],
67
+ activeOnSubPaths?: boolean
68
+ ) {
69
+ if (!includeQueryParams) {
70
+ /**
71
+ * is Active doesn't understand `href`, so we have to convert to RouteInfo-esque
72
+ */
73
+ const info = router.recognize(href);
74
+
75
+ if (info) {
76
+ const dynamicSegments = getParams(info);
77
+ const routeName = activeOnSubPaths ? info.name.replace(/\.index$/, '') : info.name;
78
+
79
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
80
+ return router.isActive(routeName, ...dynamicSegments);
81
+ }
82
+
83
+ return false;
84
+ }
85
+
86
+ const url = new URL(href, location.origin);
87
+ const hrefQueryParams = new URLSearchParams(url.searchParams);
88
+ const hrefPath = url.pathname;
89
+
90
+ const currentPath = router.currentURL?.split('?')[0];
91
+
92
+ if (!currentPath) return false;
93
+
94
+ if (activeOnSubPaths ? !currentPath.startsWith(hrefPath) : hrefPath !== currentPath) return false;
95
+
96
+ const currentQueryParams = router.currentRoute?.queryParams;
97
+
98
+ if (!currentQueryParams) return false;
99
+
100
+ if (includeQueryParams === true) {
101
+ return Object.entries(currentQueryParams).every(([key, value]) => {
102
+ return hrefQueryParams.get(key) === value;
103
+ });
104
+ }
105
+
106
+ return includeQueryParams.every((key) => {
107
+ return hrefQueryParams.get(key) === currentQueryParams[key];
108
+ });
109
+ }
110
+
111
+ type RouteInfo = ReturnType<RouterService['recognize']>;
112
+
113
+ export function getParams(currentRouteInfo: RouteInfo) {
114
+ let params: Record<string, unknown>[] = [];
115
+
116
+ while (currentRouteInfo?.parent) {
117
+ const currentParams = currentRouteInfo.params;
118
+
119
+ params = currentParams ? [currentParams, ...params] : params;
120
+ currentRouteInfo = currentRouteInfo.parent;
121
+ }
122
+
123
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
124
+ return params.map(Object.values).flat();
125
+ }
@@ -0,0 +1,25 @@
1
+ import Helper from '@ember/component/helper';
2
+ import { assert } from '@ember/debug';
3
+ import { getOwner } from '@ember/owner';
4
+
5
+ import type { Registry } from '@ember/service';
6
+ import type Service from '@ember/service';
7
+
8
+ export interface Signature<Key extends keyof Registry> {
9
+ Args: {
10
+ Positional: [Key];
11
+ };
12
+ Return: Registry[Key] & Service;
13
+ }
14
+
15
+ export default class GetService<Key extends keyof Registry> extends Helper<Signature<Key>> {
16
+ compute(positional: [Key]): Registry[Key] & Service {
17
+ const owner = getOwner(this);
18
+
19
+ assert(`Could not get owner.`, owner);
20
+
21
+ return owner.lookup(`service:${positional[0]}`) as Registry[Key] & Service;
22
+ }
23
+ }
24
+
25
+ export const service = GetService;
package/src/helpers.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { link } from './helpers/link.ts';
2
+ export { service } from './helpers/service.ts';
package/src/iframe.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Returns true if the current frame is within an iframe.
3
+ *
4
+ * ```gjs
5
+ * import { inIframe } from 'ember-primitives/iframe';
6
+ *
7
+ * <template>
8
+ * {{#if (inFrame)}}
9
+ * only show content in an iframe
10
+ * {{/if}}
11
+ * </template>
12
+ * ```
13
+ */
14
+ export const inIframe = () => window.self !== window.top;
15
+
16
+ /**
17
+ * Returns true if the current frame is not within an iframe.
18
+ *
19
+ * ```gjs
20
+ * import { notInIframe } from 'ember-primitives/iframe';
21
+ *
22
+ * <template>
23
+ * {{#if (notInIframe)}}
24
+ * only show content when not in an iframe
25
+ * This is also the default if your site/app
26
+ * does not use iframes
27
+ * {{/if}}
28
+ * </template>
29
+ * ```
30
+ */
31
+ export const notInIframe = () => !inIframe();