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.
- package/bin/index.mjs +271 -0
- package/declarations/color-scheme.d.ts +1 -1
- package/declarations/color-scheme.d.ts.map +1 -1
- 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/color-scheme.js +13 -4
- package/dist/color-scheme.js.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 +177 -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,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO: make template-only component,
|
|
3
|
+
* and use class-based modifier?
|
|
4
|
+
*
|
|
5
|
+
* This would require that modifiers could run pre-render
|
|
6
|
+
*/
|
|
7
|
+
import { hash } from '@ember/helper';
|
|
8
|
+
import { on } from '@ember/modifier';
|
|
9
|
+
|
|
10
|
+
import { link } from '../helpers/link.ts';
|
|
11
|
+
import { ExternalLink } from './external-link.gts';
|
|
12
|
+
|
|
13
|
+
import type { TOC } from '@ember/component/template-only';
|
|
14
|
+
|
|
15
|
+
export interface Signature {
|
|
16
|
+
Element: HTMLAnchorElement;
|
|
17
|
+
Args: {
|
|
18
|
+
/**
|
|
19
|
+
* the `href` string value to set on the anchor element.
|
|
20
|
+
*/
|
|
21
|
+
href: string;
|
|
22
|
+
/**
|
|
23
|
+
* When calculating the "active" state of the link, you may decide
|
|
24
|
+
* whether or not you want to _require_ that all query params be considered (true)
|
|
25
|
+
* or specify individual query params, ignoring anything not specified.
|
|
26
|
+
*
|
|
27
|
+
* For example:
|
|
28
|
+
*
|
|
29
|
+
* ```gjs live preview
|
|
30
|
+
* import { Link } from 'ember-primitives';
|
|
31
|
+
*
|
|
32
|
+
* <template>
|
|
33
|
+
* <Link @href="/" @includeActiveQueryParams={{true}} as |a|>
|
|
34
|
+
* ...
|
|
35
|
+
* </Link>
|
|
36
|
+
* </template>
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* the data-active state here will only be "true" on
|
|
40
|
+
* - `/`
|
|
41
|
+
* - `/?foo=2`
|
|
42
|
+
* - `/?foo=&bar=`
|
|
43
|
+
*
|
|
44
|
+
*/
|
|
45
|
+
includeActiveQueryParams?: true | string[];
|
|
46
|
+
/**
|
|
47
|
+
* When calculating the "active" state of the link, you may decide
|
|
48
|
+
* whether or not you want to consider sub paths to be active when
|
|
49
|
+
* child routes/urls are active.
|
|
50
|
+
*
|
|
51
|
+
* For example:
|
|
52
|
+
*
|
|
53
|
+
* ```gjs live preview
|
|
54
|
+
* import { Link } from 'ember-primitives';
|
|
55
|
+
*
|
|
56
|
+
* <template>
|
|
57
|
+
* <Link @href="/forum/1" @activeOnSubPaths={{true}} as |a|>
|
|
58
|
+
* ...
|
|
59
|
+
* </Link>
|
|
60
|
+
* </template>
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* the data-active state here will be "true" on
|
|
64
|
+
* - `/forum/1`
|
|
65
|
+
* - `/forum/1/posts`
|
|
66
|
+
* - `/forum/1/posts/comments`
|
|
67
|
+
* - `/forum/1/*etc*`
|
|
68
|
+
*
|
|
69
|
+
* if `@activeOnSubPaths` is set to false or left off
|
|
70
|
+
* the data-active state here will only be "true" on
|
|
71
|
+
* - `/forum/1`
|
|
72
|
+
*
|
|
73
|
+
*/
|
|
74
|
+
activeOnSubPaths?: true;
|
|
75
|
+
};
|
|
76
|
+
Blocks: {
|
|
77
|
+
default: [
|
|
78
|
+
{
|
|
79
|
+
/**
|
|
80
|
+
* Indicates if the passed `href` is pointing to an external site.
|
|
81
|
+
* Useful if you want your links to have additional context for when
|
|
82
|
+
* a user is about to leave your site.
|
|
83
|
+
*
|
|
84
|
+
* For example:
|
|
85
|
+
*
|
|
86
|
+
* ```gjs live preview
|
|
87
|
+
* import { Link } from 'ember-primitives';
|
|
88
|
+
*
|
|
89
|
+
* const MyLink = <template>
|
|
90
|
+
* <Link @href={{@href}} as |a|>
|
|
91
|
+
* {{yield}}
|
|
92
|
+
* {{#if a.isExternal}}
|
|
93
|
+
* ➚
|
|
94
|
+
* {{/if}}
|
|
95
|
+
* </Link>
|
|
96
|
+
* </template>;
|
|
97
|
+
*
|
|
98
|
+
* <template>
|
|
99
|
+
* <MyLink @href="https://developer.mozilla.org">MDN</MyLink>
|
|
100
|
+
* <MyLink @href="/">Home</MyLink>
|
|
101
|
+
* </template>
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
isExternal: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Indicates if the passed `href` is *active*, or the user is on the same basepath.
|
|
107
|
+
* This allows consumers to style their link if they wish or style their text.
|
|
108
|
+
* The active state will also be present on a `data-active` attribute on the generated anchor tag.
|
|
109
|
+
*
|
|
110
|
+
*
|
|
111
|
+
* For example
|
|
112
|
+
* ```gjs
|
|
113
|
+
* import { Link, service } from 'ember-primitives';
|
|
114
|
+
*
|
|
115
|
+
* const MyLink = <template>
|
|
116
|
+
* <Link @href="..."> as |a|>
|
|
117
|
+
* <span class="{{if a.isActive 'underline'}}">
|
|
118
|
+
* {{yield}}
|
|
119
|
+
* </span>
|
|
120
|
+
* </Link>
|
|
121
|
+
* </template>
|
|
122
|
+
*
|
|
123
|
+
* <template>
|
|
124
|
+
* {{#let (service 'router') as |router|}}
|
|
125
|
+
* <MyLink @href={{router.currentURL}}>Ths page</MyLink>
|
|
126
|
+
* <MyLink @href="/">Home</MyLink>
|
|
127
|
+
* {{/let}}
|
|
128
|
+
* </template>
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* By default, the query params are omitted from `isActive` calculation, but you may
|
|
132
|
+
* configure the query params to be included if you wish
|
|
133
|
+
* See: `@includeActiveQueryParams`
|
|
134
|
+
*
|
|
135
|
+
* By default, only the exact route/url is considered for the `isActive` calculation,
|
|
136
|
+
* but you may configure sub routes/paths to also be considered active
|
|
137
|
+
* See: `@activeOnSubPaths`
|
|
138
|
+
*
|
|
139
|
+
* Note that external links are never active.
|
|
140
|
+
*/
|
|
141
|
+
isActive: boolean;
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* A light wrapper around the [Anchor element][mdn-a], which will appropriately make your link an external link if the passed `@href` is not on the same domain.
|
|
149
|
+
*
|
|
150
|
+
*
|
|
151
|
+
* [mdn-a]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
|
|
152
|
+
*/
|
|
153
|
+
export const Link: TOC<Signature> = <template>
|
|
154
|
+
{{#let (link @href includeActiveQueryParams=@includeActiveQueryParams activeOnSubPaths=@activeOnSubPaths) as |l|}}
|
|
155
|
+
{{#if l.isExternal}}
|
|
156
|
+
<ExternalLink href={{@href}} ...attributes>
|
|
157
|
+
{{yield (hash isExternal=true isActive=false)}}
|
|
158
|
+
</ExternalLink>
|
|
159
|
+
{{else}}
|
|
160
|
+
<a
|
|
161
|
+
data-active={{l.isActive}}
|
|
162
|
+
href={{if @href @href "##missing##"}}
|
|
163
|
+
{{on "click" l.handleClick}}
|
|
164
|
+
...attributes
|
|
165
|
+
>
|
|
166
|
+
{{yield (hash isExternal=false isActive=l.isActive)}}
|
|
167
|
+
</a>
|
|
168
|
+
{{/if}}
|
|
169
|
+
{{/let}}
|
|
170
|
+
</template>;
|
|
171
|
+
|
|
172
|
+
export default Link;
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import Component from "@glimmer/component";
|
|
2
|
+
import { hash } from "@ember/helper";
|
|
3
|
+
import { on } from "@ember/modifier";
|
|
4
|
+
import { guidFor } from "@ember/object/internals";
|
|
5
|
+
|
|
6
|
+
import { modifier as eModifier } from "ember-modifier";
|
|
7
|
+
import { cell } from "ember-resources";
|
|
8
|
+
import { getTabster, getTabsterAttribute, MoverDirections, setTabsterAttribute } from "tabster";
|
|
9
|
+
|
|
10
|
+
import { Link, type Signature as LinkSignature } from "./link.gts";
|
|
11
|
+
import { Popover, type Signature as PopoverSignature } from "./popover.gts";
|
|
12
|
+
|
|
13
|
+
import type { TOC } from "@ember/component/template-only";
|
|
14
|
+
import type { WithBoundArgs } from "@glint/template";
|
|
15
|
+
|
|
16
|
+
type Cell<V> = ReturnType<typeof cell<V>>;
|
|
17
|
+
type LinkArgs = LinkSignature["Args"];
|
|
18
|
+
type PopoverArgs = PopoverSignature["Args"];
|
|
19
|
+
type PopoverBlockParams = PopoverSignature["Blocks"]["default"][0];
|
|
20
|
+
|
|
21
|
+
const TABSTER_CONFIG_CONTENT = getTabsterAttribute(
|
|
22
|
+
{
|
|
23
|
+
mover: {
|
|
24
|
+
direction: MoverDirections.Both,
|
|
25
|
+
cyclic: true,
|
|
26
|
+
},
|
|
27
|
+
deloser: {},
|
|
28
|
+
},
|
|
29
|
+
true,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const TABSTER_CONFIG_TRIGGER = {
|
|
33
|
+
deloser: {},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export interface Signature {
|
|
37
|
+
Args: PopoverArgs;
|
|
38
|
+
Blocks: {
|
|
39
|
+
default: [
|
|
40
|
+
{
|
|
41
|
+
arrow: PopoverBlockParams["arrow"];
|
|
42
|
+
trigger: WithBoundArgs<
|
|
43
|
+
typeof trigger,
|
|
44
|
+
"triggerElement" | "contentId" | "isOpen" | "setReference"
|
|
45
|
+
>;
|
|
46
|
+
Trigger: WithBoundArgs<typeof Trigger, "triggerModifier">;
|
|
47
|
+
Content: WithBoundArgs<
|
|
48
|
+
typeof Content,
|
|
49
|
+
"triggerElement" | "contentId" | "isOpen" | "PopoverContent"
|
|
50
|
+
>;
|
|
51
|
+
isOpen: boolean;
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SeparatorSignature {
|
|
58
|
+
Element: HTMLDivElement;
|
|
59
|
+
Blocks: { default: [] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const Separator: TOC<SeparatorSignature> = <template>
|
|
63
|
+
<div role="separator" ...attributes>
|
|
64
|
+
{{yield}}
|
|
65
|
+
</div>
|
|
66
|
+
</template>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* We focus items on `pointerMove` to achieve the following:
|
|
70
|
+
*
|
|
71
|
+
* - Mouse over an item (it focuses)
|
|
72
|
+
* - Leave mouse where it is and use keyboard to focus a different item
|
|
73
|
+
* - Wiggle mouse without it leaving previously focused item
|
|
74
|
+
* - Previously focused item should re-focus
|
|
75
|
+
*
|
|
76
|
+
* If we used `mouseOver`/`mouseEnter` it would not re-focus when the mouse
|
|
77
|
+
* wiggles. This is to match native menu implementation.
|
|
78
|
+
*/
|
|
79
|
+
function focusOnHover(e: PointerEvent) {
|
|
80
|
+
const item = e.currentTarget;
|
|
81
|
+
|
|
82
|
+
if (item instanceof HTMLElement) {
|
|
83
|
+
item?.focus();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface PrivateItemSignature {
|
|
88
|
+
Element: HTMLButtonElement;
|
|
89
|
+
Args: { onSelect?: (event: Event) => void; toggle: () => void };
|
|
90
|
+
Blocks: { default: [] };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ItemSignature {
|
|
94
|
+
Element: PrivateItemSignature["Element"];
|
|
95
|
+
Args: Omit<PrivateItemSignature["Args"], "toggle">;
|
|
96
|
+
Blocks: PrivateItemSignature["Blocks"];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const Item: TOC<PrivateItemSignature> = <template>
|
|
100
|
+
{{! @glint-expect-error }}
|
|
101
|
+
{{#let (if @onSelect (modifier on "click" @onSelect)) as |maybeClick|}}
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
role="menuitem"
|
|
105
|
+
{{! @glint-expect-error }}
|
|
106
|
+
{{maybeClick}}
|
|
107
|
+
{{on "click" @toggle}}
|
|
108
|
+
{{on "pointermove" focusOnHover}}
|
|
109
|
+
...attributes
|
|
110
|
+
>
|
|
111
|
+
{{yield}}
|
|
112
|
+
</button>
|
|
113
|
+
{{/let}}
|
|
114
|
+
</template>;
|
|
115
|
+
|
|
116
|
+
interface LinkItemArgs extends LinkArgs {
|
|
117
|
+
toggle: () => void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface PrivateLinkItemSignature {
|
|
121
|
+
Element: HTMLAnchorElement;
|
|
122
|
+
Args: LinkItemArgs;
|
|
123
|
+
Blocks: { default: [] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface LinkItemSignature {
|
|
127
|
+
Element: PrivateLinkItemSignature["Element"];
|
|
128
|
+
Args: LinkArgs;
|
|
129
|
+
Blocks: PrivateLinkItemSignature["Blocks"];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const LinkItem: TOC<PrivateLinkItemSignature> = <template>
|
|
133
|
+
<Link
|
|
134
|
+
role="menuitem"
|
|
135
|
+
@href={{@href}}
|
|
136
|
+
@includeActiveQueryParams={{@includeActiveQueryParams}}
|
|
137
|
+
@activeOnSubPaths={{@activeOnSubPaths}}
|
|
138
|
+
{{on "click" @toggle}}
|
|
139
|
+
{{on "pointermove" focusOnHover}}
|
|
140
|
+
...attributes
|
|
141
|
+
>
|
|
142
|
+
{{yield}}
|
|
143
|
+
</Link>
|
|
144
|
+
</template>;
|
|
145
|
+
|
|
146
|
+
const installContent = eModifier<{
|
|
147
|
+
Element: HTMLElement;
|
|
148
|
+
Args: {
|
|
149
|
+
Named: {
|
|
150
|
+
isOpen: Cell<boolean>;
|
|
151
|
+
triggerElement: Cell<HTMLElement>;
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
}>((element, _: [], { isOpen, triggerElement }) => {
|
|
155
|
+
// focus first focusable element on the content
|
|
156
|
+
const tabster = getTabster(window);
|
|
157
|
+
const firstFocusable = tabster?.focusable.findFirst({
|
|
158
|
+
container: element,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
firstFocusable?.focus();
|
|
162
|
+
|
|
163
|
+
// listen for "outside" clicks
|
|
164
|
+
function onDocumentClick(e: MouseEvent) {
|
|
165
|
+
if (
|
|
166
|
+
isOpen.current &&
|
|
167
|
+
e.target &&
|
|
168
|
+
!element.contains(e.target as HTMLElement) &&
|
|
169
|
+
!triggerElement.current?.contains(e.target as HTMLElement)
|
|
170
|
+
) {
|
|
171
|
+
isOpen.current = false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// listen for the escape key
|
|
176
|
+
function onDocumentKeydown(e: KeyboardEvent) {
|
|
177
|
+
if (isOpen.current && e.key === "Escape") {
|
|
178
|
+
isOpen.current = false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
document.addEventListener("click", onDocumentClick);
|
|
183
|
+
document.addEventListener("keydown", onDocumentKeydown);
|
|
184
|
+
|
|
185
|
+
return () => {
|
|
186
|
+
document.removeEventListener("click", onDocumentClick);
|
|
187
|
+
document.removeEventListener("keydown", onDocumentKeydown);
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
interface PrivateContentSignature {
|
|
192
|
+
Element: HTMLDivElement;
|
|
193
|
+
Args: {
|
|
194
|
+
triggerElement: Cell<HTMLElement>;
|
|
195
|
+
contentId: string;
|
|
196
|
+
isOpen: Cell<boolean>;
|
|
197
|
+
PopoverContent: PopoverBlockParams["Content"];
|
|
198
|
+
};
|
|
199
|
+
Blocks: {
|
|
200
|
+
default: [
|
|
201
|
+
{
|
|
202
|
+
Item: WithBoundArgs<typeof Item, "toggle">;
|
|
203
|
+
LinkItem: WithBoundArgs<typeof LinkItem, "toggle">;
|
|
204
|
+
Separator: typeof Separator;
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface ContentSignature {
|
|
211
|
+
Element: PrivateContentSignature["Element"];
|
|
212
|
+
Blocks: PrivateContentSignature["Blocks"];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const Content: TOC<PrivateContentSignature> = <template>
|
|
216
|
+
{{#if @isOpen.current}}
|
|
217
|
+
<@PopoverContent
|
|
218
|
+
id={{@contentId}}
|
|
219
|
+
role="menu"
|
|
220
|
+
data-tabster={{TABSTER_CONFIG_CONTENT}}
|
|
221
|
+
tabindex="0"
|
|
222
|
+
{{installContent isOpen=@isOpen triggerElement=@triggerElement}}
|
|
223
|
+
...attributes
|
|
224
|
+
>
|
|
225
|
+
{{yield
|
|
226
|
+
(hash
|
|
227
|
+
Item=(component Item toggle=@isOpen.toggle)
|
|
228
|
+
LinkItem=(component LinkItem toggle=@isOpen.toggle)
|
|
229
|
+
Separator=Separator
|
|
230
|
+
)
|
|
231
|
+
}}
|
|
232
|
+
</@PopoverContent>
|
|
233
|
+
{{/if}}
|
|
234
|
+
</template>;
|
|
235
|
+
|
|
236
|
+
interface PrivateTriggerModifierSignature {
|
|
237
|
+
Element: HTMLElement;
|
|
238
|
+
Args: {
|
|
239
|
+
Named: {
|
|
240
|
+
triggerElement: Cell<HTMLElement>;
|
|
241
|
+
isOpen: Cell<boolean>;
|
|
242
|
+
contentId: string;
|
|
243
|
+
setReference: PopoverBlockParams["setReference"];
|
|
244
|
+
stopPropagation?: boolean;
|
|
245
|
+
preventDefault?: boolean;
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface TriggerModifierSignature {
|
|
251
|
+
Element: PrivateTriggerModifierSignature["Element"];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const trigger = eModifier<PrivateTriggerModifierSignature>(
|
|
255
|
+
(
|
|
256
|
+
element,
|
|
257
|
+
_: [],
|
|
258
|
+
{ triggerElement, isOpen, contentId, setReference, stopPropagation, preventDefault },
|
|
259
|
+
) => {
|
|
260
|
+
element.setAttribute("aria-haspopup", "menu");
|
|
261
|
+
|
|
262
|
+
if (isOpen.current) {
|
|
263
|
+
element.setAttribute("aria-controls", contentId);
|
|
264
|
+
element.setAttribute("aria-expanded", "true");
|
|
265
|
+
} else {
|
|
266
|
+
element.removeAttribute("aria-controls");
|
|
267
|
+
element.setAttribute("aria-expanded", "false");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
setTabsterAttribute(element, TABSTER_CONFIG_TRIGGER);
|
|
271
|
+
|
|
272
|
+
const onTriggerClick = (event: MouseEvent) => {
|
|
273
|
+
if (stopPropagation) {
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (preventDefault) {
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
isOpen.toggle();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
element.addEventListener("click", onTriggerClick);
|
|
285
|
+
|
|
286
|
+
triggerElement.current = element;
|
|
287
|
+
|
|
288
|
+
setReference(element);
|
|
289
|
+
|
|
290
|
+
return () => {
|
|
291
|
+
element.removeEventListener("click", onTriggerClick);
|
|
292
|
+
};
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
interface PrivateTriggerSignature {
|
|
297
|
+
Element: HTMLButtonElement;
|
|
298
|
+
Args: {
|
|
299
|
+
triggerModifier: WithBoundArgs<
|
|
300
|
+
typeof trigger,
|
|
301
|
+
"triggerElement" | "contentId" | "isOpen" | "setReference"
|
|
302
|
+
>;
|
|
303
|
+
stopPropagation?: boolean;
|
|
304
|
+
preventDefault?: boolean;
|
|
305
|
+
};
|
|
306
|
+
Blocks: { default: [] };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export interface TriggerSignature {
|
|
310
|
+
Element: PrivateTriggerSignature["Element"];
|
|
311
|
+
Blocks: PrivateTriggerSignature["Blocks"];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const Trigger: TOC<PrivateTriggerSignature> = <template>
|
|
315
|
+
<button
|
|
316
|
+
type="button"
|
|
317
|
+
{{@triggerModifier stopPropagation=@stopPropagation preventDefault=@preventDefault}}
|
|
318
|
+
...attributes
|
|
319
|
+
>
|
|
320
|
+
{{yield}}
|
|
321
|
+
</button>
|
|
322
|
+
</template>;
|
|
323
|
+
|
|
324
|
+
const IsOpen = () => cell<boolean>(false);
|
|
325
|
+
const TriggerElement = () => cell<HTMLElement>();
|
|
326
|
+
|
|
327
|
+
export class Menu extends Component<Signature> {
|
|
328
|
+
contentId = guidFor(this);
|
|
329
|
+
|
|
330
|
+
<template>
|
|
331
|
+
{{#let (IsOpen) (TriggerElement) as |isOpen triggerEl|}}
|
|
332
|
+
<Popover
|
|
333
|
+
@flipOptions={{@flipOptions}}
|
|
334
|
+
@middleware={{@middleware}}
|
|
335
|
+
@offsetOptions={{@offsetOptions}}
|
|
336
|
+
@placement={{@placement}}
|
|
337
|
+
@shiftOptions={{@shiftOptions}}
|
|
338
|
+
@strategy={{@strategy}}
|
|
339
|
+
@inline={{@inline}}
|
|
340
|
+
as |p|
|
|
341
|
+
>
|
|
342
|
+
{{#let
|
|
343
|
+
(modifier
|
|
344
|
+
trigger
|
|
345
|
+
triggerElement=triggerEl
|
|
346
|
+
isOpen=isOpen
|
|
347
|
+
contentId=this.contentId
|
|
348
|
+
setReference=p.setReference
|
|
349
|
+
)
|
|
350
|
+
as |triggerModifier|
|
|
351
|
+
}}
|
|
352
|
+
{{yield
|
|
353
|
+
(hash
|
|
354
|
+
trigger=triggerModifier
|
|
355
|
+
Trigger=(component Trigger triggerModifier=triggerModifier)
|
|
356
|
+
Content=(component
|
|
357
|
+
Content
|
|
358
|
+
PopoverContent=p.Content
|
|
359
|
+
isOpen=isOpen
|
|
360
|
+
triggerElement=triggerEl
|
|
361
|
+
contentId=this.contentId
|
|
362
|
+
)
|
|
363
|
+
arrow=p.arrow
|
|
364
|
+
isOpen=isOpen.current
|
|
365
|
+
)
|
|
366
|
+
}}
|
|
367
|
+
{{/let}}
|
|
368
|
+
</Popover>
|
|
369
|
+
{{/let}}
|
|
370
|
+
</template>
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export default Menu;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { assert } from "@ember/debug";
|
|
2
|
+
import { on } from "@ember/modifier";
|
|
3
|
+
|
|
4
|
+
import type { TOC } from "@ember/component/template-only";
|
|
5
|
+
|
|
6
|
+
const reset = (event: Event) => {
|
|
7
|
+
assert("[BUG]: reset called without an event.target", event.target instanceof HTMLElement);
|
|
8
|
+
|
|
9
|
+
const form = event.target.closest("form");
|
|
10
|
+
|
|
11
|
+
assert(
|
|
12
|
+
"Form is missing. Cannot use <Reset> without being contained within a <form>",
|
|
13
|
+
form instanceof HTMLFormElement,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
form.reset();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const Submit: TOC<{
|
|
20
|
+
Element: HTMLButtonElement;
|
|
21
|
+
Blocks: { default: [] };
|
|
22
|
+
}> = <template>
|
|
23
|
+
<button type="submit" ...attributes>Submit</button>
|
|
24
|
+
</template>;
|
|
25
|
+
|
|
26
|
+
export const Reset: TOC<{
|
|
27
|
+
Element: HTMLButtonElement;
|
|
28
|
+
Blocks: { default: [] };
|
|
29
|
+
}> = <template>
|
|
30
|
+
<button type="button" {{on "click" reset}} ...attributes>{{yield}}</button>
|
|
31
|
+
</template>;
|