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.
- package/bin/index.mjs +271 -0
- package/declarations/components/rating/public-types.d.ts +0 -4
- package/declarations/components/rating/public-types.d.ts.map +1 -1
- package/declarations/components/rating/rating.d.ts +9 -1
- package/declarations/components/rating/rating.d.ts.map +1 -1
- package/declarations/components/rating/stars.d.ts.map +1 -1
- package/declarations/components/rating/state.d.ts +4 -0
- package/declarations/components/rating/state.d.ts.map +1 -1
- package/declarations/components/rating/utils.d.ts +0 -1
- package/declarations/components/rating/utils.d.ts.map +1 -1
- package/dist/components/rating.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{rating-CjBVsX6q.js → rating-BrIiwDLw.js} +21 -17
- package/dist/rating-BrIiwDLw.js.map +1 -0
- package/package.json +6 -2
- package/src/-private.ts +4 -0
- package/src/color-scheme.ts +165 -0
- package/src/components/-private/typed-elements.gts +13 -0
- package/src/components/-private/utils.ts +16 -0
- package/src/components/accordion/content.gts +34 -0
- package/src/components/accordion/header.gts +36 -0
- package/src/components/accordion/item.gts +55 -0
- package/src/components/accordion/public.ts +64 -0
- package/src/components/accordion/trigger.gts +32 -0
- package/src/components/accordion.gts +195 -0
- package/src/components/avatar.gts +108 -0
- package/src/components/dialog.gts +234 -0
- package/src/components/external-link.gts +14 -0
- package/src/components/form.gts +75 -0
- package/src/components/heading.gts +36 -0
- package/src/components/keys.gts +53 -0
- package/src/components/layout/hero.css +5 -0
- package/src/components/layout/hero.gts +17 -0
- package/src/components/layout/sticky-footer.css +9 -0
- package/src/components/layout/sticky-footer.gts +40 -0
- package/src/components/link.gts +172 -0
- package/src/components/menu.gts +373 -0
- package/src/components/one-time-password/buttons.gts +31 -0
- package/src/components/one-time-password/input.gts +198 -0
- package/src/components/one-time-password/otp.gts +130 -0
- package/src/components/one-time-password/utils.ts +201 -0
- package/src/components/one-time-password.gts +2 -0
- package/src/components/popover.gts +248 -0
- package/src/components/portal-targets.gts +136 -0
- package/src/components/portal.gts +194 -0
- package/src/components/progress.gts +154 -0
- package/src/components/rating/public-types.ts +44 -0
- package/src/components/rating/range.gts +22 -0
- package/src/components/rating/rating.gts +228 -0
- package/src/components/rating/stars.gts +60 -0
- package/src/components/rating/state.gts +144 -0
- package/src/components/rating/utils.ts +7 -0
- package/src/components/rating.gts +5 -0
- package/src/components/scroller.gts +179 -0
- package/src/components/shadowed.gts +110 -0
- package/src/components/switch.gts +103 -0
- package/src/components/tabs.gts +519 -0
- package/src/components/toggle-group.gts +265 -0
- package/src/components/toggle.gts +81 -0
- package/src/components/violations.css +105 -0
- package/src/components/violations.css.ts +1 -0
- package/src/components/visually-hidden.css +14 -0
- package/src/components/visually-hidden.gts +15 -0
- package/src/components/zoetrope/index.gts +358 -0
- package/src/components/zoetrope/styles.css +40 -0
- package/src/components/zoetrope/types.ts +65 -0
- package/src/components/zoetrope.ts +3 -0
- package/src/dom-context.gts +245 -0
- package/src/floating-ui/component.gts +186 -0
- package/src/floating-ui/middleware.ts +13 -0
- package/src/floating-ui/modifier.ts +183 -0
- package/src/floating-ui.ts +2 -0
- package/src/head.gts +37 -0
- package/src/helpers/body-class.ts +94 -0
- package/src/helpers/link.ts +125 -0
- package/src/helpers/service.ts +25 -0
- package/src/helpers.ts +2 -0
- package/src/iframe.ts +31 -0
- package/src/index.ts +43 -0
- package/src/load.gts +77 -0
- package/src/narrowing.ts +7 -0
- package/src/on-resize.ts +64 -0
- package/src/proper-links.ts +140 -0
- package/src/qp.ts +107 -0
- package/src/resize-observer.ts +132 -0
- package/src/service.ts +103 -0
- package/src/store.ts +72 -0
- package/src/styles.css.ts +5 -0
- package/src/tabster.ts +54 -0
- package/src/template-registry.ts +44 -0
- package/src/test-support/a11y.ts +50 -0
- package/src/test-support/dom.ts +112 -0
- package/src/test-support/otp.ts +64 -0
- package/src/test-support/rating.ts +144 -0
- package/src/test-support/routing.ts +62 -0
- package/src/test-support/zoetrope.ts +51 -0
- package/src/test-support.gts +6 -0
- package/src/type-utils.ts +1 -0
- package/src/utils.ts +75 -0
- package/src/viewport/in-viewport.gts +128 -0
- package/src/viewport/viewport.ts +122 -0
- package/src/viewport.ts +2 -0
- 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
|
+
);
|
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
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();
|