ember-primitives 0.48.2 → 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/portal.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/declarations/tabster.d.ts.map +1 -1
- package/declarations/utils.d.ts.map +1 -1
- package/declarations/viewport/in-viewport.d.ts +70 -0
- package/declarations/viewport/in-viewport.d.ts.map +1 -0
- package/declarations/viewport/viewport.d.ts +59 -0
- package/declarations/viewport/viewport.d.ts.map +1 -0
- package/declarations/viewport.d.ts +3 -0
- package/declarations/viewport.d.ts.map +1 -0
- package/dist/-private.js +0 -1
- package/dist/-private.js.map +1 -1
- package/dist/color-scheme.js +0 -1
- package/dist/color-scheme.js.map +1 -1
- package/dist/{component-Bs3N-G9z.js → component-BXy_iafw.js} +2 -3
- package/dist/component-BXy_iafw.js.map +1 -0
- package/dist/components/accordion.js +5 -6
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/avatar.js +3 -4
- package/dist/components/avatar.js.map +1 -1
- package/dist/components/dialog.js +2 -3
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/external-link.js +1 -2
- package/dist/components/external-link.js.map +1 -1
- package/dist/components/form.js +1 -2
- package/dist/components/form.js.map +1 -1
- package/dist/components/heading.js +1 -2
- package/dist/components/heading.js.map +1 -1
- package/dist/components/keys.js +2 -3
- package/dist/components/keys.js.map +1 -1
- package/dist/components/layout/hero.js +1 -1
- package/dist/components/layout/sticky-footer.js +1 -1
- package/dist/components/link.js +1 -2
- package/dist/components/link.js.map +1 -1
- package/dist/components/menu.js +6 -8
- package/dist/components/menu.js.map +1 -1
- package/dist/components/one-time-password.js +1 -2
- package/dist/components/popover.js +3 -4
- package/dist/components/popover.js.map +1 -1
- package/dist/components/portal-targets.js +2 -3
- package/dist/components/portal-targets.js.map +1 -1
- package/dist/components/portal.js +3 -7
- package/dist/components/portal.js.map +1 -1
- package/dist/components/progress.js +2 -3
- package/dist/components/progress.js.map +1 -1
- package/dist/components/rating.js +1 -2
- package/dist/components/scroller.js +1 -2
- package/dist/components/scroller.js.map +1 -1
- package/dist/components/shadowed.js +2 -3
- package/dist/components/shadowed.js.map +1 -1
- package/dist/components/switch.js +5 -6
- package/dist/components/switch.js.map +1 -1
- package/dist/components/tabs.js +6 -7
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/toggle-group.js +3 -4
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle.js +2 -3
- package/dist/components/toggle.js.map +1 -1
- package/dist/components/visually-hidden.js +1 -2
- package/dist/components/visually-hidden.js.map +1 -1
- package/dist/components/zoetrope.js +1 -2
- package/dist/dom-context.js +2 -3
- package/dist/dom-context.js.map +1 -1
- package/dist/floating-ui.js +1 -2
- package/dist/head.js +1 -2
- package/dist/head.js.map +1 -1
- package/dist/helpers/body-class.js +0 -1
- package/dist/helpers/body-class.js.map +1 -1
- package/dist/helpers/link.js +0 -1
- package/dist/helpers/link.js.map +1 -1
- package/dist/helpers/service.js +0 -1
- package/dist/helpers/service.js.map +1 -1
- package/dist/helpers.js +0 -1
- package/dist/helpers.js.map +1 -1
- package/dist/iframe.js +0 -1
- package/dist/iframe.js.map +1 -1
- package/dist/{index-DKE67I8L.js → index-gRO4Cvlf.js} +2 -2
- package/dist/index-gRO4Cvlf.js.map +1 -0
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/load.js +0 -1
- package/dist/load.js.map +1 -1
- package/dist/narrowing.js +0 -1
- package/dist/narrowing.js.map +1 -1
- package/dist/on-resize.js +0 -1
- package/dist/on-resize.js.map +1 -1
- package/dist/{otp-C6hCCXKx.js → otp-7rz1PWP0.js} +6 -7
- package/dist/otp-7rz1PWP0.js.map +1 -0
- package/dist/proper-links.js +0 -1
- package/dist/proper-links.js.map +1 -1
- package/dist/qp.js +0 -1
- package/dist/qp.js.map +1 -1
- package/dist/rating-BrIiwDLw.js +152 -0
- package/dist/rating-BrIiwDLw.js.map +1 -0
- package/dist/resize-observer.js +0 -1
- package/dist/resize-observer.js.map +1 -1
- package/dist/service.js +0 -1
- package/dist/service.js.map +1 -1
- package/dist/store.js +0 -1
- package/dist/store.js.map +1 -1
- package/dist/styles.css.js +0 -1
- package/dist/tabster.js +0 -1
- package/dist/tabster.js.map +1 -1
- package/dist/test-support.js +0 -1
- package/dist/test-support.js.map +1 -1
- package/dist/{utils-C5796IKA.js → utils-D0v9WKmV.js} +1 -2
- package/dist/utils-D0v9WKmV.js.map +1 -0
- package/dist/utils.js +4 -1
- package/dist/utils.js.map +1 -1
- package/dist/viewport/in-viewport.js +82 -0
- package/dist/viewport/in-viewport.js.map +1 -0
- package/dist/viewport/viewport.js +92 -0
- package/dist/viewport/viewport.js.map +1 -0
- package/dist/viewport.js +3 -0
- package/dist/viewport.js.map +1 -0
- package/package.json +24 -20
- 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/component-Bs3N-G9z.js.map +0 -1
- package/dist/index-DKE67I8L.js.map +0 -1
- package/dist/otp-C6hCCXKx.js.map +0 -1
- package/dist/rating-D052JWRa.js +0 -149
- package/dist/rating-D052JWRa.js.map +0 -1
- package/dist/utils-C5796IKA.js.map +0 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import Component from "@glimmer/component";
|
|
2
|
+
import { tracked } from "@glimmer/tracking";
|
|
3
|
+
import { assert } from "@ember/debug";
|
|
4
|
+
import { hash } from "@ember/helper";
|
|
5
|
+
import { on } from "@ember/modifier";
|
|
6
|
+
|
|
7
|
+
import { modifier as eModifier } from "ember-modifier";
|
|
8
|
+
// temp
|
|
9
|
+
// https://github.com/tracked-tools/tracked-toolbox/issues/38
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
11
|
+
// @ts-expect-error
|
|
12
|
+
import { localCopy } from "tracked-toolbox";
|
|
13
|
+
|
|
14
|
+
import type { TOC } from "@ember/component/template-only";
|
|
15
|
+
import type { ModifierLike, WithBoundArgs } from "@glint/template";
|
|
16
|
+
|
|
17
|
+
const DialogElement: TOC<{
|
|
18
|
+
Element: HTMLDialogElement;
|
|
19
|
+
Args: {
|
|
20
|
+
/**
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
open: boolean | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
onClose: () => void;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
register: ModifierLike<{ Element: HTMLDialogElement }>;
|
|
33
|
+
};
|
|
34
|
+
Blocks: { default: [] };
|
|
35
|
+
}> = <template>
|
|
36
|
+
<dialog ...attributes open={{@open}} {{on "close" @onClose}} {{@register}}>
|
|
37
|
+
{{yield}}
|
|
38
|
+
</dialog>
|
|
39
|
+
</template>;
|
|
40
|
+
|
|
41
|
+
export interface Signature {
|
|
42
|
+
Args: {
|
|
43
|
+
/**
|
|
44
|
+
* Optionally set the open state of the `<dialog>`
|
|
45
|
+
* The state will still be managed internally,
|
|
46
|
+
* so this does not need to be a maintained value, but whenever it changes,
|
|
47
|
+
* the dialog element will reflect that change accordingly.
|
|
48
|
+
*/
|
|
49
|
+
open?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* When the `<dialog>` is closed, this function will be called
|
|
52
|
+
* and the `<dialog>`'s `returnValue` will be passed.
|
|
53
|
+
*
|
|
54
|
+
* This can be used to determine which button was clicked to close the modal
|
|
55
|
+
*
|
|
56
|
+
* Note though that this value is only populated when using
|
|
57
|
+
* `<form method='dialog'>`
|
|
58
|
+
*/
|
|
59
|
+
onClose?: (returnValue: string) => void;
|
|
60
|
+
};
|
|
61
|
+
Blocks: {
|
|
62
|
+
default: [
|
|
63
|
+
{
|
|
64
|
+
/**
|
|
65
|
+
* Represents the open state of the `<dialog>` element.
|
|
66
|
+
*/
|
|
67
|
+
isOpen: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Closes the `<dialog>` element
|
|
71
|
+
* Will throw an error if `Dialog` is not rendered.
|
|
72
|
+
*/
|
|
73
|
+
close: () => void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Opens the `<dialog>` element.
|
|
77
|
+
* Will throw an error if `Dialog` is not rendered.
|
|
78
|
+
*/
|
|
79
|
+
open: () => void;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* This modifier should be applied to the button that opens the Dialog so that it can be re-focused when the dialog closes.
|
|
83
|
+
*
|
|
84
|
+
* Example:
|
|
85
|
+
*
|
|
86
|
+
* ```gjs
|
|
87
|
+
* <template>
|
|
88
|
+
* <Modal as |m|>
|
|
89
|
+
* <button {{m.focusOnClose}} {{on "click" m.open}}>Open</button>
|
|
90
|
+
*
|
|
91
|
+
* <m.Dialog>...</m.Dialog>
|
|
92
|
+
* </Modal>
|
|
93
|
+
* </template>
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
focusOnClose: ModifierLike<{ Element: HTMLElement }>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* This is the `<dialog>` element (with some defaults pre-wired).
|
|
100
|
+
* This is required to be rendered.
|
|
101
|
+
*/
|
|
102
|
+
Dialog: WithBoundArgs<typeof DialogElement, "onClose" | "register" | "open">;
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class ModalDialog extends Component<Signature> {
|
|
109
|
+
<template>
|
|
110
|
+
{{yield
|
|
111
|
+
(hash
|
|
112
|
+
isOpen=this.isOpen
|
|
113
|
+
open=this.open
|
|
114
|
+
close=this.close
|
|
115
|
+
focusOnClose=this.refocus
|
|
116
|
+
Dialog=(component DialogElement open=@open onClose=this.handleClose register=this.register)
|
|
117
|
+
)
|
|
118
|
+
}}
|
|
119
|
+
</template>
|
|
120
|
+
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
122
|
+
@localCopy("args.open") declare _isOpen: boolean;
|
|
123
|
+
|
|
124
|
+
get isOpen() {
|
|
125
|
+
/**
|
|
126
|
+
* Always fallback to false (closed)
|
|
127
|
+
*/
|
|
128
|
+
return this._isOpen ?? false;
|
|
129
|
+
}
|
|
130
|
+
set isOpen(val: boolean) {
|
|
131
|
+
this._isOpen = val;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#lastIsOpen = false;
|
|
135
|
+
refocus = eModifier((element) => {
|
|
136
|
+
assert(`focusOnClose is only valid on a HTMLElement`, element instanceof HTMLElement);
|
|
137
|
+
|
|
138
|
+
if (!this.isOpen && this.#lastIsOpen) {
|
|
139
|
+
element.focus();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.#lastIsOpen = this.isOpen;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
@tracked declare dialogElement: HTMLDialogElement | undefined;
|
|
146
|
+
|
|
147
|
+
register = eModifier((element: HTMLDialogElement) => {
|
|
148
|
+
/**
|
|
149
|
+
* This is very sad.
|
|
150
|
+
*
|
|
151
|
+
* But we need the element to be 'root state'
|
|
152
|
+
* so that when we read things like "isOpen",
|
|
153
|
+
* when the dialog is finally rendered, all the
|
|
154
|
+
* downstream properties render.
|
|
155
|
+
*
|
|
156
|
+
* This has to be an async / delayed a bit, so that
|
|
157
|
+
* the tracking frame can exit, and we don't infinite loop
|
|
158
|
+
*/
|
|
159
|
+
void (async () => {
|
|
160
|
+
await Promise.resolve();
|
|
161
|
+
|
|
162
|
+
this.dialogElement = element;
|
|
163
|
+
})();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Closes the dialog -- this will throw an error in development if the dialog element was not rendered
|
|
168
|
+
*/
|
|
169
|
+
close = () => {
|
|
170
|
+
assert(
|
|
171
|
+
"Cannot call `close` on <Dialog> without rendering the dialog element.",
|
|
172
|
+
this.dialogElement,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* If the element is already closed, don't run all this again
|
|
177
|
+
*/
|
|
178
|
+
if (!this.dialogElement.hasAttribute("open")) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* removes the `open` attribute
|
|
184
|
+
* handleClose will be called because the dialog has bound the `close` event.
|
|
185
|
+
*/
|
|
186
|
+
this.dialogElement.close();
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @internal
|
|
191
|
+
*
|
|
192
|
+
* handles the <dialog> element's native close behavior.
|
|
193
|
+
* listened to via addEventListener('close', ...);
|
|
194
|
+
*/
|
|
195
|
+
handleClose = () => {
|
|
196
|
+
assert(
|
|
197
|
+
"Cannot call `handleDialogClose` on <Dialog> without rendering the dialog element. This is likely a bug in ember-primitives. Please open an issue <3",
|
|
198
|
+
this.dialogElement,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
this.isOpen = false;
|
|
202
|
+
this.args.onClose?.(this.dialogElement.returnValue);
|
|
203
|
+
// the return value ends up staying... which is annoying
|
|
204
|
+
this.dialogElement.returnValue = "";
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Opens the dialog -- this will throw an error in development if the dialog element was not rendered
|
|
209
|
+
*/
|
|
210
|
+
open = () => {
|
|
211
|
+
assert(
|
|
212
|
+
"Cannot call `open` on <Dialog> without rendering the dialog element.",
|
|
213
|
+
this.dialogElement,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* If the element is already open, don't run all this again
|
|
218
|
+
*/
|
|
219
|
+
if (this.dialogElement.hasAttribute("open")) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* adds the `open` attribute
|
|
225
|
+
*/
|
|
226
|
+
this.dialogElement.showModal();
|
|
227
|
+
this.isOpen = true;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const Modal = ModalDialog;
|
|
232
|
+
export const Dialog = ModalDialog;
|
|
233
|
+
|
|
234
|
+
export default ModalDialog;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TOC } from "@ember/component/template-only";
|
|
2
|
+
|
|
3
|
+
export const ExternalLink: TOC<{
|
|
4
|
+
Element: HTMLAnchorElement;
|
|
5
|
+
Blocks: {
|
|
6
|
+
default: [];
|
|
7
|
+
};
|
|
8
|
+
}> = <template>
|
|
9
|
+
<a target="_blank" rel="noreferrer noopener" href="##missing##" ...attributes>
|
|
10
|
+
{{yield}}
|
|
11
|
+
</a>
|
|
12
|
+
</template>;
|
|
13
|
+
|
|
14
|
+
export default ExternalLink;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { fn } from "@ember/helper";
|
|
2
|
+
import { on } from "@ember/modifier";
|
|
3
|
+
|
|
4
|
+
import { dataFrom } from "form-data-utils";
|
|
5
|
+
|
|
6
|
+
import type { TOC } from "@ember/component/template-only";
|
|
7
|
+
|
|
8
|
+
type Data = ReturnType<typeof dataFrom>;
|
|
9
|
+
|
|
10
|
+
export const dataFromEvent = dataFrom;
|
|
11
|
+
|
|
12
|
+
const handleInput = (
|
|
13
|
+
onChange: (data: Data, eventType: "input" | "submit", event: Event) => void,
|
|
14
|
+
event: Event | SubmitEvent,
|
|
15
|
+
eventType: "input" | "submit" = "input",
|
|
16
|
+
) => {
|
|
17
|
+
const data = dataFrom(event);
|
|
18
|
+
|
|
19
|
+
onChange(data, eventType, event);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const handleSubmit = (
|
|
23
|
+
onChange: (data: Data, eventType: "input" | "submit", event: Event | SubmitEvent) => void,
|
|
24
|
+
event: SubmitEvent,
|
|
25
|
+
) => {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
handleInput(onChange, event, "submit");
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export interface Signature {
|
|
31
|
+
Element: HTMLFormElement;
|
|
32
|
+
Args: {
|
|
33
|
+
/**
|
|
34
|
+
* Any time the value of any field is changed this function will be called.
|
|
35
|
+
*/
|
|
36
|
+
onChange: (
|
|
37
|
+
/**
|
|
38
|
+
* The data from the form as an Object of `{ [field name] => value }` pairs.
|
|
39
|
+
* This is generated from the native [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
|
|
40
|
+
*
|
|
41
|
+
* Additional fields/inputs/controls can be added to this data by specifying a
|
|
42
|
+
* "name" attribute.
|
|
43
|
+
*/
|
|
44
|
+
data: Data,
|
|
45
|
+
/**
|
|
46
|
+
* Indicates whether the `onChange` function was called from the `input` or `submit` event handlers.
|
|
47
|
+
*/
|
|
48
|
+
eventType: "input" | "submit",
|
|
49
|
+
/**
|
|
50
|
+
* The raw event, if needed.
|
|
51
|
+
*/
|
|
52
|
+
event: Event | SubmitEvent,
|
|
53
|
+
) => void;
|
|
54
|
+
};
|
|
55
|
+
Blocks: {
|
|
56
|
+
/**
|
|
57
|
+
* The main content for the form. This is where inputs / fields / controls would go.
|
|
58
|
+
* Within the `<form>` content, `<button type="submit">` will submit the form, which
|
|
59
|
+
* triggers the `@onChange` event.
|
|
60
|
+
*/
|
|
61
|
+
default: [];
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const Form: TOC<Signature> = <template>
|
|
66
|
+
<form
|
|
67
|
+
{{on "input" (fn handleInput @onChange)}}
|
|
68
|
+
{{on "submit" (fn handleSubmit @onChange)}}
|
|
69
|
+
...attributes
|
|
70
|
+
>
|
|
71
|
+
{{yield}}
|
|
72
|
+
</form>
|
|
73
|
+
</template>;
|
|
74
|
+
|
|
75
|
+
export default Form;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Component from "@glimmer/component";
|
|
2
|
+
|
|
3
|
+
import { element } from "ember-element-helper";
|
|
4
|
+
import { getSectionHeadingLevel } from "which-heading-do-i-need";
|
|
5
|
+
|
|
6
|
+
import type Owner from "@ember/owner";
|
|
7
|
+
|
|
8
|
+
export class Heading extends Component<{
|
|
9
|
+
Element: HTMLElement;
|
|
10
|
+
Blocks: { default: [] };
|
|
11
|
+
}> {
|
|
12
|
+
headingScopeAnchor: Text;
|
|
13
|
+
constructor(owner: Owner, args: object) {
|
|
14
|
+
super(owner, args);
|
|
15
|
+
|
|
16
|
+
this.headingScopeAnchor = document.createTextNode("");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get level() {
|
|
20
|
+
return getSectionHeadingLevel(this.headingScopeAnchor);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get hLevel() {
|
|
24
|
+
return `h${this.level}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
{{this.headingScopeAnchor}}
|
|
29
|
+
|
|
30
|
+
{{#let (element this.hLevel) as |El|}}
|
|
31
|
+
<El ...attributes>
|
|
32
|
+
{{yield}}
|
|
33
|
+
</El>
|
|
34
|
+
{{/let}}
|
|
35
|
+
</template>
|
|
36
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TOC } from "@ember/component/template-only";
|
|
2
|
+
|
|
3
|
+
const isLast = (collection: unknown[], index: number) => index === collection.length - 1;
|
|
4
|
+
const isNotLast = (collection: unknown[], index: number) => !isLast(collection, index);
|
|
5
|
+
const isMac = navigator.userAgent.indexOf("Mac OS") >= 0;
|
|
6
|
+
|
|
7
|
+
function split(str: string) {
|
|
8
|
+
const keys = str.split("+").map((x) => x.trim());
|
|
9
|
+
|
|
10
|
+
return keys;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getKeys(keys: string[] | string, mac?: string[] | string) {
|
|
14
|
+
const normalKeys = Array.isArray(keys) ? keys : split(keys);
|
|
15
|
+
|
|
16
|
+
if (!mac) {
|
|
17
|
+
return normalKeys;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const normalMac = Array.isArray(mac) ? mac : split(mac);
|
|
21
|
+
|
|
22
|
+
return isMac ? normalMac : normalKeys;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface KeyComboSignature {
|
|
26
|
+
Element: HTMLElement;
|
|
27
|
+
Args: {
|
|
28
|
+
keys: string[] | string;
|
|
29
|
+
mac?: string[] | string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const KeyCombo: TOC<KeyComboSignature> = <template>
|
|
34
|
+
<span class="ember-primitives__key-combination" ...attributes>
|
|
35
|
+
{{#let (getKeys @keys @mac) as |keys|}}
|
|
36
|
+
{{#each keys as |key i|}}
|
|
37
|
+
<Key>{{key}}</Key>
|
|
38
|
+
{{#if (isNotLast keys i)}}
|
|
39
|
+
<span class="ember-primitives__key-combination__separator">+</span>
|
|
40
|
+
{{/if}}
|
|
41
|
+
{{/each}}
|
|
42
|
+
{{/let}}
|
|
43
|
+
</span>
|
|
44
|
+
</template>;
|
|
45
|
+
|
|
46
|
+
export interface KeySignature {
|
|
47
|
+
Element: HTMLElement;
|
|
48
|
+
Blocks: { default?: [] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const Key: TOC<KeySignature> = <template>
|
|
52
|
+
<kbd class="ember-primitives__key" ...attributes>{{yield}}</kbd>
|
|
53
|
+
</template>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "./hero.css";
|
|
2
|
+
|
|
3
|
+
import type { TOC } from "@ember/component/template-only";
|
|
4
|
+
|
|
5
|
+
export const Hero: TOC<{
|
|
6
|
+
/**
|
|
7
|
+
* The wrapper element of the whole layout.
|
|
8
|
+
*/
|
|
9
|
+
Element: HTMLDivElement;
|
|
10
|
+
Blocks: {
|
|
11
|
+
default: [];
|
|
12
|
+
};
|
|
13
|
+
}> = <template>
|
|
14
|
+
<div class="ember-primitives__hero__wrapper" ...attributes>
|
|
15
|
+
{{yield}}
|
|
16
|
+
</div>
|
|
17
|
+
</template>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import "./sticky-footer.css";
|
|
2
|
+
|
|
3
|
+
import type { TOC } from "@ember/component/template-only";
|
|
4
|
+
|
|
5
|
+
export const StickyFooter: TOC<{
|
|
6
|
+
/**
|
|
7
|
+
* The wrapper element of the whole layout.
|
|
8
|
+
* Valid parents for this element must have either a set height,
|
|
9
|
+
* or a set max-height.
|
|
10
|
+
*/
|
|
11
|
+
Element: HTMLDivElement;
|
|
12
|
+
Blocks: {
|
|
13
|
+
/**
|
|
14
|
+
* This is the scrollable content, contained within a `<div>` element for positioning.
|
|
15
|
+
* If this component is used as the main layout on a page,
|
|
16
|
+
* the `<main>` element would be appropriate within here.
|
|
17
|
+
*/
|
|
18
|
+
content: [];
|
|
19
|
+
/**
|
|
20
|
+
* This is the footer content, contained within a `<div>` element for positioning.
|
|
21
|
+
* A `<footer>` element would be appropriate within here.
|
|
22
|
+
*
|
|
23
|
+
* This element will be at the bottom of the page if the content does not overflow the containing element and this element will be at the bottom of the content if there is overflow.
|
|
24
|
+
*/
|
|
25
|
+
footer: [];
|
|
26
|
+
};
|
|
27
|
+
}> = <template>
|
|
28
|
+
<div class="ember-primitives__sticky-footer__wrapper" ...attributes>
|
|
29
|
+
<div class="ember-primitives__sticky-footer__container">
|
|
30
|
+
<div class="ember-primitives__sticky-footer__content">
|
|
31
|
+
{{yield to="content"}}
|
|
32
|
+
</div>
|
|
33
|
+
<div class="ember-primitives__sticky-footer__footer">
|
|
34
|
+
{{yield to="footer"}}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</template>;
|
|
39
|
+
|
|
40
|
+
export default StickyFooter;
|
|
@@ -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;
|