ember-primitives 0.27.2 → 0.28.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/declarations/color-scheme.d.ts.map +1 -1
- package/declarations/components/-private/typed-elements.d.ts +1 -1
- package/declarations/components/accordion/content.d.ts +2 -2
- package/declarations/components/accordion/header.d.ts +2 -2
- package/declarations/components/accordion/item.d.ts +3 -3
- package/declarations/components/accordion/public.d.ts +3 -3
- package/declarations/components/accordion/trigger.d.ts +2 -2
- package/declarations/components/accordion.d.ts +7 -7
- package/declarations/components/accordion.d.ts.map +1 -1
- package/declarations/components/avatar.d.ts +4 -4
- package/declarations/components/dialog.d.ts +30 -4
- package/declarations/components/dialog.d.ts.map +1 -1
- package/declarations/components/external-link.d.ts +1 -1
- package/declarations/components/form.d.ts +3 -3
- package/declarations/components/keys.d.ts +17 -0
- package/declarations/components/keys.d.ts.map +1 -0
- package/declarations/components/layout/hero.d.ts +2 -2
- package/declarations/components/layout/sticky-footer.d.ts +2 -2
- package/declarations/components/menu.d.ts +19 -19
- package/declarations/components/menu.d.ts.map +1 -1
- package/declarations/components/one-time-password/buttons.d.ts +1 -1
- package/declarations/components/one-time-password/index.d.ts +2 -2
- package/declarations/components/one-time-password/input.d.ts +5 -5
- package/declarations/components/one-time-password/input.d.ts.map +1 -1
- package/declarations/components/one-time-password/otp.d.ts +5 -5
- package/declarations/components/one-time-password/otp.d.ts.map +1 -1
- package/declarations/components/one-time-password/utils.d.ts.map +1 -1
- package/declarations/components/popover.d.ts +17 -26
- package/declarations/components/popover.d.ts.map +1 -1
- package/declarations/components/portal-targets.d.ts +1 -1
- package/declarations/components/portal.d.ts +4 -3
- package/declarations/components/portal.d.ts.map +1 -1
- package/declarations/components/progress.d.ts +4 -4
- package/declarations/components/scroller.d.ts +1 -1
- package/declarations/components/shadowed.d.ts +1 -1
- package/declarations/components/switch.d.ts +5 -5
- package/declarations/components/toggle-group.d.ts +4 -4
- package/declarations/components/toggle.d.ts +2 -2
- package/declarations/components/toggle.d.ts.map +1 -1
- package/declarations/components/zoetrope/index.d.ts +3 -3
- package/declarations/components/zoetrope.d.ts +2 -2
- package/declarations/floating-ui/component.d.ts +19 -16
- package/declarations/floating-ui/component.d.ts.map +1 -1
- package/declarations/floating-ui/modifier.d.ts.map +1 -1
- package/declarations/floating-ui.d.ts +1 -1
- package/declarations/helpers/link.d.ts.map +1 -1
- package/declarations/index.d.ts +20 -19
- package/declarations/index.d.ts.map +1 -1
- package/declarations/proper-links.d.ts.map +1 -1
- package/declarations/test-support/routing.d.ts.map +1 -1
- package/declarations/utils.d.ts.map +1 -1
- package/dist/color-scheme.js +5 -5
- package/dist/color-scheme.js.map +1 -1
- package/dist/components/-private/typed-elements.js.map +1 -1
- package/dist/components/-private/utils.js +1 -3
- package/dist/components/-private/utils.js.map +1 -1
- package/dist/components/accordion/content.js +1 -1
- package/dist/components/accordion/header.js +1 -1
- package/dist/components/accordion/item.js +1 -1
- package/dist/components/accordion/trigger.js +1 -1
- package/dist/components/accordion.js +26 -25
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/avatar.js.map +1 -1
- package/dist/components/dialog.js +24 -15
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/external-link.js.map +1 -1
- package/dist/components/form.js +6 -6
- package/dist/components/form.js.map +1 -1
- package/dist/components/keys.js +33 -0
- package/dist/components/keys.js.map +1 -0
- package/dist/components/layout/hero.js.map +1 -1
- package/dist/components/layout/sticky-footer.js.map +1 -1
- package/dist/components/link.js.map +1 -1
- package/dist/components/menu.js +47 -46
- package/dist/components/menu.js.map +1 -1
- package/dist/components/one-time-password/buttons.js +5 -5
- package/dist/components/one-time-password/buttons.js.map +1 -1
- package/dist/components/one-time-password/input.js +14 -14
- package/dist/components/one-time-password/input.js.map +1 -1
- package/dist/components/one-time-password/otp.js +26 -25
- package/dist/components/one-time-password/otp.js.map +1 -1
- package/dist/components/one-time-password/utils.js +18 -18
- package/dist/components/one-time-password/utils.js.map +1 -1
- package/dist/components/popover.js +44 -44
- package/dist/components/popover.js.map +1 -1
- package/dist/components/portal-targets.js +16 -16
- package/dist/components/portal-targets.js.map +1 -1
- package/dist/components/portal.js +5 -3
- package/dist/components/portal.js.map +1 -1
- package/dist/components/progress.js +16 -16
- package/dist/components/progress.js.map +1 -1
- package/dist/components/scroller.js +6 -6
- package/dist/components/scroller.js.map +1 -1
- package/dist/components/shadowed.js +9 -9
- package/dist/components/shadowed.js.map +1 -1
- package/dist/components/switch.js.map +1 -1
- package/dist/components/toggle-group.js +24 -24
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle.js +4 -4
- package/dist/components/toggle.js.map +1 -1
- package/dist/components/violations.css +8 -8
- package/dist/components/zoetrope/index.js +102 -102
- package/dist/components/zoetrope/index.js.map +1 -1
- package/dist/floating-ui/component.js +6 -6
- package/dist/floating-ui/component.js.map +1 -1
- package/dist/floating-ui/modifier.js +10 -6
- package/dist/floating-ui/modifier.js.map +1 -1
- package/dist/helpers/link.js +11 -7
- package/dist/helpers/link.js.map +1 -1
- package/dist/helpers/service.js +1 -1
- package/dist/helpers/service.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/{item-DmpElnSZ.js → item-D6pwWzMs.js} +3 -3
- package/dist/item-D6pwWzMs.js.map +1 -0
- package/dist/proper-links.js +10 -10
- package/dist/proper-links.js.map +1 -1
- package/dist/tabster.js +2 -2
- package/dist/tabster.js.map +1 -1
- package/dist/test-support/a11y.js +4 -4
- package/dist/test-support/a11y.js.map +1 -1
- package/dist/test-support/otp.js +6 -6
- package/dist/test-support/otp.js.map +1 -1
- package/dist/test-support/routing.js +3 -2
- package/dist/test-support/routing.js.map +1 -1
- package/dist/test-support/zoetrope.js.map +1 -1
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -1
- package/package.json +20 -30
- package/dist/item-DmpElnSZ.js.map +0 -1
|
@@ -16,9 +16,9 @@ const DialogElement = setComponentTemplate(precompileTemplate("\n <dialog ...at
|
|
|
16
16
|
on
|
|
17
17
|
})
|
|
18
18
|
}), templateOnly());
|
|
19
|
-
|
|
19
|
+
class ModalDialog extends Component {
|
|
20
20
|
static {
|
|
21
|
-
setComponentTemplate(precompileTemplate("\n {{yield (hash isOpen=this.isOpen open=this.open close=this.close Dialog=(component DialogElement open=@open onClose=this.handleClose register=this.register))}}\n ", {
|
|
21
|
+
setComponentTemplate(precompileTemplate("\n {{yield (hash isOpen=this.isOpen open=this.open close=this.close focusOnClose=this.refocus Dialog=(component DialogElement open=@open onClose=this.handleClose register=this.register))}}\n ", {
|
|
22
22
|
strictMode: true,
|
|
23
23
|
scope: () => ({
|
|
24
24
|
hash,
|
|
@@ -26,8 +26,9 @@ let ModalDialog = class ModalDialog extends Component {
|
|
|
26
26
|
})
|
|
27
27
|
}), this);
|
|
28
28
|
}
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
29
30
|
static {
|
|
30
|
-
g(this.prototype, "_isOpen", [localCopy(
|
|
31
|
+
g(this.prototype, "_isOpen", [localCopy("args.open")]);
|
|
31
32
|
}
|
|
32
33
|
#_isOpen = (i(this, "_isOpen"), void 0);
|
|
33
34
|
get isOpen() {
|
|
@@ -36,14 +37,22 @@ let ModalDialog = class ModalDialog extends Component {
|
|
|
36
37
|
*/
|
|
37
38
|
return this._isOpen ?? false;
|
|
38
39
|
}
|
|
39
|
-
set isOpen(
|
|
40
|
-
this._isOpen =
|
|
40
|
+
set isOpen(val) {
|
|
41
|
+
this._isOpen = val;
|
|
41
42
|
}
|
|
43
|
+
#lastIsOpen = false;
|
|
44
|
+
refocus = modifier(element => {
|
|
45
|
+
assert(`focusOnClose is only valid on a HTMLElement`, element instanceof HTMLElement);
|
|
46
|
+
if (!this.isOpen && this.#lastIsOpen) {
|
|
47
|
+
element.focus();
|
|
48
|
+
}
|
|
49
|
+
this.#lastIsOpen = this.isOpen;
|
|
50
|
+
});
|
|
42
51
|
static {
|
|
43
52
|
g(this.prototype, "dialogElement", [tracked]);
|
|
44
53
|
}
|
|
45
54
|
#dialogElement = (i(this, "dialogElement"), void 0);
|
|
46
|
-
register = modifier(
|
|
55
|
+
register = modifier(element => {
|
|
47
56
|
/**
|
|
48
57
|
* This is very sad.
|
|
49
58
|
*
|
|
@@ -55,20 +64,20 @@ let ModalDialog = class ModalDialog extends Component {
|
|
|
55
64
|
* This has to be an async / delayed a bit, so that
|
|
56
65
|
* the tracking frame can exit, and we don't infinite loop
|
|
57
66
|
*/
|
|
58
|
-
(async () => {
|
|
67
|
+
void (async () => {
|
|
59
68
|
await Promise.resolve();
|
|
60
|
-
this.dialogElement =
|
|
69
|
+
this.dialogElement = element;
|
|
61
70
|
})();
|
|
62
71
|
});
|
|
63
72
|
/**
|
|
64
73
|
* Closes the dialog -- this will throw an error in development if the dialog element was not rendered
|
|
65
74
|
*/
|
|
66
75
|
close = () => {
|
|
67
|
-
assert(
|
|
76
|
+
assert("Cannot call `close` on <Dialog> without rendering the dialog element.", this.dialogElement);
|
|
68
77
|
/**
|
|
69
78
|
* If the element is already closed, don't run all this again
|
|
70
79
|
*/
|
|
71
|
-
if (!this.dialogElement.hasAttribute(
|
|
80
|
+
if (!this.dialogElement.hasAttribute("open")) {
|
|
72
81
|
return;
|
|
73
82
|
}
|
|
74
83
|
/**
|
|
@@ -84,21 +93,21 @@ let ModalDialog = class ModalDialog extends Component {
|
|
|
84
93
|
* listened to via addEventListener('close', ...);
|
|
85
94
|
*/
|
|
86
95
|
handleClose = () => {
|
|
87
|
-
assert(
|
|
96
|
+
assert("Cannot call `handleDialogClose` on <Dialog> without rendering the dialog element. This is likely a bug in ember-primitives. Please open an issue <3", this.dialogElement);
|
|
88
97
|
this.isOpen = false;
|
|
89
98
|
this.args.onClose?.(this.dialogElement.returnValue);
|
|
90
99
|
// the return value ends up staying... which is annoying
|
|
91
|
-
this.dialogElement.returnValue =
|
|
100
|
+
this.dialogElement.returnValue = "";
|
|
92
101
|
};
|
|
93
102
|
/**
|
|
94
103
|
* Opens the dialog -- this will throw an error in development if the dialog element was not rendered
|
|
95
104
|
*/
|
|
96
105
|
open = () => {
|
|
97
|
-
assert(
|
|
106
|
+
assert("Cannot call `open` on <Dialog> without rendering the dialog element.", this.dialogElement);
|
|
98
107
|
/**
|
|
99
108
|
* If the element is already open, don't run all this again
|
|
100
109
|
*/
|
|
101
|
-
if (this.dialogElement.hasAttribute(
|
|
110
|
+
if (this.dialogElement.hasAttribute("open")) {
|
|
102
111
|
return;
|
|
103
112
|
}
|
|
104
113
|
/**
|
|
@@ -107,7 +116,7 @@ let ModalDialog = class ModalDialog extends Component {
|
|
|
107
116
|
this.dialogElement.showModal();
|
|
108
117
|
this.isOpen = true;
|
|
109
118
|
};
|
|
110
|
-
}
|
|
119
|
+
}
|
|
111
120
|
const Modal = ModalDialog;
|
|
112
121
|
const Dialog = ModalDialog;
|
|
113
122
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dialog.js","sources":["../../src/components/dialog.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\nimport { tracked } from \"@glimmer/tracking\";\nimport { assert } from \"@ember/debug\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\n\nimport { modifier as eModifier } from \"ember-modifier\";\n// temp\n// https://github.com/tracked-tools/tracked-toolbox/issues/38\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error\nimport { localCopy } from \"tracked-toolbox\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type { ModifierLike, WithBoundArgs } from \"@glint/template\";\n\nconst DialogElement: TOC<{\n Element: HTMLDialogElement;\n Args: {\n /**\n * @internal\n */\n open: boolean | undefined;\n /**\n * @internal\n */\n onClose: () => void;\n\n /**\n * @internal\n */\n register: ModifierLike<{ Element: HTMLDialogElement }>;\n };\n Blocks: { default: [] };\n}> = <template>\n <dialog ...attributes open={{@open}} {{on \"close\" @onClose}} {{@register}}>\n {{yield}}\n </dialog>\n</template>;\n\nexport interface Signature {\n Args: {\n /**\n * Optionally set the open state of the `<dialog>`\n * The state will still be managed internally,\n * so this does not need to be a maintained value, but whenever it changes,\n * the dialog element will reflect that change accordingly.\n */\n open?: boolean;\n /**\n * When the `<dialog>` is closed, this function will be called\n * and the `<dialog>`'s `returnValue` will be passed.\n *\n * This can be used to determine which button was clicked to close the modal\n *\n * Note though that this value is only populated when using\n * `<form method='dialog'>`\n */\n onClose?: (returnValue: string) => void;\n };\n Blocks: {\n default: [\n {\n /**\n * Represents the open state of the `<dialog>` element.\n */\n isOpen: boolean;\n\n /**\n * Closes the `<dialog>` element\n * Will throw an error if `Dialog` is not rendered.\n */\n close: () => void;\n\n /**\n * Opens the `<dialog>` element.\n * Will throw an error if `Dialog` is not rendered.\n */\n open: () => void;\n\n /**\n * This modifier should be applied to the button that opens the Dialog so that it can be re-focused when the dialog closes.\n *\n * Example:\n *\n * ```gjs\n * <template>\n * <Modal as |m|>\n * <button {{m.focusOnClose}} {{on \"click\" m.open}}>Open</button>\n *\n * <m.Dialog>...</m.Dialog>\n * </Modal>\n * </template>\n * ```\n */\n focusOnClose: ModifierLike<{ Element: HTMLElement }>;\n\n /**\n * This is the `<dialog>` element (with some defaults pre-wired).\n * This is required to be rendered.\n */\n Dialog: WithBoundArgs<typeof DialogElement, \"onClose\" | \"register\" | \"open\">;\n },\n ];\n };\n}\n\nclass ModalDialog extends Component<Signature> {\n <template>\n {{yield\n (hash\n isOpen=this.isOpen\n open=this.open\n close=this.close\n focusOnClose=this.refocus\n Dialog=(component DialogElement open=@open onClose=this.handleClose register=this.register)\n )\n }}\n </template>\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n @localCopy(\"args.open\") declare _isOpen: boolean;\n\n get isOpen() {\n /**\n * Always fallback to false (closed)\n */\n return this._isOpen ?? false;\n }\n set isOpen(val: boolean) {\n this._isOpen = val;\n }\n\n #lastIsOpen = false;\n refocus = eModifier((element) => {\n assert(`focusOnClose is only valid on a HTMLElement`, element instanceof HTMLElement);\n\n if (!this.isOpen && this.#lastIsOpen) {\n element.focus();\n }\n\n this.#lastIsOpen = this.isOpen;\n });\n\n @tracked declare dialogElement: HTMLDialogElement | undefined;\n\n register = eModifier((element: HTMLDialogElement) => {\n /**\n * This is very sad.\n *\n * But we need the element to be 'root state'\n * so that when we read things like \"isOpen\",\n * when the dialog is finally rendered, all the\n * downstream properties render.\n *\n * This has to be an async / delayed a bit, so that\n * the tracking frame can exit, and we don't infinite loop\n */\n void (async () => {\n await Promise.resolve();\n\n this.dialogElement = element;\n })();\n });\n\n /**\n * Closes the dialog -- this will throw an error in development if the dialog element was not rendered\n */\n close = () => {\n assert(\n \"Cannot call `close` on <Dialog> without rendering the dialog element.\",\n this.dialogElement,\n );\n\n /**\n * If the element is already closed, don't run all this again\n */\n if (!this.dialogElement.hasAttribute(\"open\")) {\n return;\n }\n\n /**\n * removes the `open` attribute\n * handleClose will be called because the dialog has bound the `close` event.\n */\n this.dialogElement.close();\n };\n\n /**\n * @internal\n *\n * handles the <dialog> element's native close behavior.\n * listened to via addEventListener('close', ...);\n */\n handleClose = () => {\n assert(\n \"Cannot call `handleDialogClose` on <Dialog> without rendering the dialog element. This is likely a bug in ember-primitives. Please open an issue <3\",\n this.dialogElement,\n );\n\n this.isOpen = false;\n this.args.onClose?.(this.dialogElement.returnValue);\n // the return value ends up staying... which is annoying\n this.dialogElement.returnValue = \"\";\n };\n\n /**\n * Opens the dialog -- this will throw an error in development if the dialog element was not rendered\n */\n open = () => {\n assert(\n \"Cannot call `open` on <Dialog> without rendering the dialog element.\",\n this.dialogElement,\n );\n\n /**\n * If the element is already open, don't run all this again\n */\n if (this.dialogElement.hasAttribute(\"open\")) {\n return;\n }\n\n /**\n * adds the `open` attribute\n */\n this.dialogElement.showModal();\n this.isOpen = true;\n };\n}\n\nexport const Modal = ModalDialog;\nexport const Dialog = ModalDialog;\n\nexport default ModalDialog;\n"],"names":["DialogElement","setComponentTemplate","precompileTemplate","strictMode","scope","on","templateOnly","ModalDialog","Component","hash","g","prototype","localCopy","i","void 0","isOpen","_isOpen","val","refocus","eModifier","element","assert","HTMLElement","focus","tracked","register","Promise","resolve","dialogElement","close","hasAttribute","handleClose","args","onClose","returnValue","open","showModal","Modal","Dialog"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external-link.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"external-link.js","sources":["../../src/components/external-link.gts"],"sourcesContent":["import type { TOC } from \"@ember/component/template-only\";\n\nexport const ExternalLink: TOC<{\n Element: HTMLAnchorElement;\n Blocks: {\n default: [];\n };\n}> = <template>\n <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"##missing##\" ...attributes>\n {{yield}}\n </a>\n</template>;\n\nexport default ExternalLink;\n"],"names":["ExternalLink","setComponentTemplate","precompileTemplate","strictMode","templateOnly"],"mappings":";;;;MAEaA,YAKR,GAAAC,oBAAA,CAAAC,kBAAA,CAIL,mHAAA,EAAA;EAAAC,UAAA,EAAA;AAAU,CAAE,CAAA,EAAAC,YAAA,EAAA;;;;"}
|
package/dist/components/form.js
CHANGED
|
@@ -6,13 +6,13 @@ import { setComponentTemplate } from '@ember/component';
|
|
|
6
6
|
import templateOnly from '@ember/component/template-only';
|
|
7
7
|
|
|
8
8
|
const dataFromEvent = dataFrom;
|
|
9
|
-
const handleInput = (
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const handleInput = (onChange, event, eventType = "input") => {
|
|
10
|
+
const data = dataFrom(event);
|
|
11
|
+
onChange(data, eventType, event);
|
|
12
12
|
};
|
|
13
|
-
const handleSubmit = (
|
|
14
|
-
|
|
15
|
-
handleInput(
|
|
13
|
+
const handleSubmit = (onChange, event) => {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
handleInput(onChange, event, "submit");
|
|
16
16
|
};
|
|
17
17
|
const Form = setComponentTemplate(precompileTemplate("\n <form {{on \"input\" (fn handleInput @onChange)}} {{on \"submit\" (fn handleSubmit @onChange)}} ...attributes>\n {{yield}}\n </form>\n", {
|
|
18
18
|
strictMode: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"form.js","sources":["../../src/components/form.gts"],"sourcesContent":["import { fn } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\n\nimport { dataFrom } from \"form-data-utils\";\n\nimport type { TOC } from \"@ember/component/template-only\";\n\ntype Data = ReturnType<typeof dataFrom>;\n\nexport const dataFromEvent = dataFrom;\n\nconst handleInput = (\n onChange: (data: Data, eventType: \"input\" | \"submit\", event: Event) => void,\n event: Event | SubmitEvent,\n eventType: \"input\" | \"submit\" = \"input\",\n) => {\n const data = dataFrom(event);\n\n onChange(data, eventType, event);\n};\n\nconst handleSubmit = (\n onChange: (data: Data, eventType: \"input\" | \"submit\", event: Event | SubmitEvent) => void,\n event: SubmitEvent,\n) => {\n event.preventDefault();\n handleInput(onChange, event, \"submit\");\n};\n\nexport interface Signature {\n Element: HTMLFormElement;\n Args: {\n /**\n * Any time the value of any field is changed this function will be called.\n */\n onChange: (\n /**\n * The data from the form as an Object of `{ [field name] => value }` pairs.\n * This is generated from the native [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)\n *\n * Additional fields/inputs/controls can be added to this data by specifying a\n * \"name\" attribute.\n */\n data: Data,\n /**\n * Indicates whether the `onChange` function was called from the `input` or `submit` event handlers.\n */\n eventType: \"input\" | \"submit\",\n /**\n * The raw event, if needed.\n */\n event: Event | SubmitEvent,\n ) => void;\n };\n Blocks: {\n /**\n * The main content for the form. This is where inputs / fields / controls would go.\n * Within the `<form>` content, `<button type=\"submit\">` will submit the form, which\n * triggers the `@onChange` event.\n */\n default: [];\n };\n}\n\nexport const Form: TOC<Signature> = <template>\n <form\n {{on \"input\" (fn handleInput @onChange)}}\n {{on \"submit\" (fn handleSubmit @onChange)}}\n ...attributes\n >\n {{yield}}\n </form>\n</template>;\n\nexport default Form;\n"],"names":["dataFromEvent","dataFrom","handleInput","onChange","event","eventType","data","handleSubmit","preventDefault","Form","setComponentTemplate","precompileTemplate","strictMode","scope","on","fn","templateOnly"],"mappings":";;;;;;;AASO,MAAMA,gBAAgBC;AAE7B,MAAMC,cAAcA,CAClBC,QAA2E,EAC3EC,OACAC,YAAgC,OAAO,KAAA;AAEvC,EAAA,MAAMC,OAAOL,QAAS,CAAAG,KAAA,CAAA;AAEtBD,EAAAA,QAAA,CAASG,MAAMD,SAAW,EAAAD,KAAA,CAAA;AAC5B,CAAA;AAEA,MAAMG,YAAe,GAAAA,CACnBJ,QAAyF,EACzFC,KAAO,KAAA;EAEPA,KAAA,CAAMI,cAAc,EAAA;AACpBN,EAAAA,WAAA,CAAYC,UAAUC,KAAO,EAAA,QAAA,CAAA;AAC/B,CAAA;MAqCaK,IAAU,GAAAC,oBAAA,CAAaC,kBAAA,CAQpC,gJAAA,EAAA;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAC,EAAA;IAAAC,EAAA;IAAAb,WAAA;AAAAK,IAAAA;AAAA,GAAA;AAAU,CAAE,CAAA,EAAAS,YAAA,EAAA;;;;"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { precompileTemplate } from '@ember/template-compilation';
|
|
2
|
+
import { setComponentTemplate } from '@ember/component';
|
|
3
|
+
import templateOnly from '@ember/component/template-only';
|
|
4
|
+
|
|
5
|
+
const isLast = (collection, index) => index === collection.length - 1;
|
|
6
|
+
const isNotLast = (collection, index) => !isLast(collection, index);
|
|
7
|
+
const isMac = navigator.userAgent.indexOf("Mac OS") >= 0;
|
|
8
|
+
function split(str) {
|
|
9
|
+
const keys = str.split("+").map(x => x.trim());
|
|
10
|
+
return keys;
|
|
11
|
+
}
|
|
12
|
+
function getKeys(keys, mac) {
|
|
13
|
+
const normalKeys = Array.isArray(keys) ? keys : split(keys);
|
|
14
|
+
if (!mac) {
|
|
15
|
+
return normalKeys;
|
|
16
|
+
}
|
|
17
|
+
const normalMac = Array.isArray(mac) ? mac : split(mac);
|
|
18
|
+
return isMac ? normalMac : normalKeys;
|
|
19
|
+
}
|
|
20
|
+
const KeyCombo = setComponentTemplate(precompileTemplate("\n <span class=\"ember-primitives__key-combination\" ...attributes>\n {{#let (getKeys @keys @mac) as |keys|}}\n {{#each keys as |key i|}}\n <Key>{{key}}</Key>\n {{#if (isNotLast keys i)}}\n <span class=\"ember-primitives__key-combination__separator\">+</span>\n {{/if}}\n {{/each}}\n {{/let}}\n </span>\n", {
|
|
21
|
+
strictMode: true,
|
|
22
|
+
scope: () => ({
|
|
23
|
+
getKeys,
|
|
24
|
+
Key,
|
|
25
|
+
isNotLast
|
|
26
|
+
})
|
|
27
|
+
}), templateOnly());
|
|
28
|
+
const Key = setComponentTemplate(precompileTemplate("\n <kbd class=\"ember-primitives__key\" ...attributes>{{yield}}</kbd>\n", {
|
|
29
|
+
strictMode: true
|
|
30
|
+
}), templateOnly());
|
|
31
|
+
|
|
32
|
+
export { Key, KeyCombo };
|
|
33
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sources":["../../src/components/keys.gts"],"sourcesContent":["import type { TOC } from \"@ember/component/template-only\";\n\nconst isLast = (collection: unknown[], index: number) => index === collection.length - 1;\nconst isNotLast = (collection: unknown[], index: number) => !isLast(collection, index);\nconst isMac = navigator.userAgent.indexOf(\"Mac OS\") >= 0;\n\nfunction split(str: string) {\n const keys = str.split(\"+\").map((x) => x.trim());\n\n return keys;\n}\n\nfunction getKeys(keys: string[] | string, mac?: string[] | string) {\n const normalKeys = Array.isArray(keys) ? keys : split(keys);\n\n if (!mac) {\n return normalKeys;\n }\n\n const normalMac = Array.isArray(mac) ? mac : split(mac);\n\n return isMac ? normalMac : normalKeys;\n}\n\nexport interface KeyComboSignature {\n Element: HTMLElement;\n Args: {\n keys: string[] | string;\n mac?: string[] | string;\n };\n}\n\nexport const KeyCombo: TOC<KeyComboSignature> = <template>\n <span class=\"ember-primitives__key-combination\" ...attributes>\n {{#let (getKeys @keys @mac) as |keys|}}\n {{#each keys as |key i|}}\n <Key>{{key}}</Key>\n {{#if (isNotLast keys i)}}\n <span class=\"ember-primitives__key-combination__separator\">+</span>\n {{/if}}\n {{/each}}\n {{/let}}\n </span>\n</template>;\n\nexport interface KeySignature {\n Element: HTMLElement;\n Blocks: { default?: [] };\n}\n\nexport const Key: TOC<KeySignature> = <template>\n <kbd class=\"ember-primitives__key\" ...attributes>{{yield}}</kbd>\n</template>;\n"],"names":["isLast","collection","index","length","isNotLast","isMac","navigator","userAgent","indexOf","split","str","keys","map","x","trim","getKeys","mac","normalKeys","Array","isArray","normalMac","KeyCombo","setComponentTemplate","precompileTemplate","strictMode","scope","Key","templateOnly"],"mappings":";;;;AAEA,MAAMA,MAAA,GAASA,CAACC,UAAmB,EAAIC,KAAa,KAAKA,KAAA,KAAUD,UAAW,CAAAE,MAAM,GAAG,CAAA;AACvF,MAAMC,SAAA,GAAYA,CAACH,UAAmB,EAAIC,KAAa,KAAK,CAACF,MAAA,CAAOC,UAAY,EAAAC,KAAA,CAAA;AAChF,MAAMG,QAAQC,SAAU,CAAAC,SAAS,CAACC,OAAO,CAAC,QAAa,CAAA,IAAA,CAAA;AAEvD,SAASC,KAAAA,CAAMC,GAAW,EAAA;AACxB,EAAA,MAAMC,IAAA,GAAOD,GAAI,CAAAD,KAAK,CAAC,GAAA,CAAA,CAAKG,GAAG,CAAEC,CAAM,IAAAA,CAAA,CAAEC,IAAI,EAAA,CAAA;AAE7C,EAAA,OAAOH,IAAA;AACT;AAEA,SAASI,OAAAA,CAAQJ,IAAuB,EAAEK,GAAuB,EAAA;AAC/D,EAAA,MAAMC,aAAaC,KAAM,CAAAC,OAAO,CAACR,IAAA,CAAA,GAAQA,OAAOF,KAAM,CAAAE,IAAA,CAAA;EAEtD,IAAI,CAACK,GAAK,EAAA;AACR,IAAA,OAAOC,UAAA;AACT;AAEA,EAAA,MAAMG,YAAYF,KAAM,CAAAC,OAAO,CAACH,GAAA,CAAA,GAAOA,MAAMP,KAAM,CAAAO,GAAA,CAAA;AAEnD,EAAA,OAAOX,QAAQe,SAAY,GAAAH,UAAA;AAC7B;MAUaI,QAAc,GAAAC,oBAAA,CAAqBC,kBAAA,CAWhD,kWAAA,EAAA;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAV,OAAA;IAAAW,GAAA;AAAAtB,IAAAA;AAAA,GAAA;AAAU,CAAE,CAAA,EAAAuB,YAAA,EAAA;MAOCD,GAAS,GAAAJ,oBAAA,CAAgBC,kBAAA,CAEtC,0EAAA,EAAA;EAAAC,UAAA,EAAA;AAAU,CAAE,CAAA,EAAAG,YAAA,EAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hero.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hero.js","sources":["../../../src/components/layout/hero.gts"],"sourcesContent":["import \"./hero.css\";\n\nimport type { TOC } from \"@ember/component/template-only\";\n\nexport const Hero: TOC<{\n /**\n * The wrapper element of the whole layout.\n */\n Element: HTMLDivElement;\n Blocks: {\n default: [];\n };\n}> = <template>\n <div class=\"ember-primitives__hero__wrapper\" ...attributes>\n {{yield}}\n </div>\n</template>;\n"],"names":["Hero","setComponentTemplate","precompileTemplate","strictMode","templateOnly"],"mappings":";;;;;MAIaA,IAQR,GAAAC,oBAAA,CAAAC,kBAAA,CAIL,8FAAA,EAAA;EAAAC,UAAA,EAAA;AAAU,CAAE,CAAA,EAAAC,YAAA,EAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sticky-footer.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sticky-footer.js","sources":["../../../src/components/layout/sticky-footer.gts"],"sourcesContent":["import \"./sticky-footer.css\";\n\nimport type { TOC } from \"@ember/component/template-only\";\n\nexport const StickyFooter: TOC<{\n /**\n * The wrapper element of the whole layout.\n * Valid parents for this element must have either a set height,\n * or a set max-height.\n */\n Element: HTMLDivElement;\n Blocks: {\n /**\n * This is the scrollable content, contained within a `<div>` element for positioning.\n * If this component is used as the main layout on a page,\n * the `<main>` element would be appropriate within here.\n */\n content: [];\n /**\n * This is the footer content, contained within a `<div>` element for positioning.\n * A `<footer>` element would be appropriate within here.\n *\n * 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.\n */\n footer: [];\n };\n}> = <template>\n <div class=\"ember-primitives__sticky-footer__wrapper\" ...attributes>\n <div class=\"ember-primitives__sticky-footer__container\">\n <div class=\"ember-primitives__sticky-footer__content\">\n {{yield to=\"content\"}}\n </div>\n <div class=\"ember-primitives__sticky-footer__footer\">\n {{yield to=\"footer\"}}\n </div>\n </div>\n </div>\n</template>;\n\nexport default StickyFooter;\n"],"names":["StickyFooter","setComponentTemplate","precompileTemplate","strictMode","templateOnly"],"mappings":";;;;;MAIaA,YAsBR,GAAAC,oBAAA,CAAAC,kBAAA,CAWL,kYAAA,EAAA;EAAAC,UAAA,EAAA;AAAU,CAAE,CAAA,EAAAC,YAAA,EAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"link.js","sources":["../../src/components/link.gts"],"sourcesContent":["/**\n * TODO: make template-only component,\n * and use class-based modifier?\n *\n * This would require that modifiers could run pre-render\n */\nimport { hash } from '@ember/helper';\nimport { on } from '@ember/modifier';\n\nimport { link } from '../helpers/link.ts';\nimport { ExternalLink } from './external-link.gts';\n\nimport type { TOC } from '@ember/component/template-only';\n\nexport interface Signature {\n Element: HTMLAnchorElement;\n Args: {\n /**\n * the `href` string value to set on the anchor element.\n */\n href: string;\n /**\n * When calculating the \"active\" state of the link, you may decide\n * whether or not you want to _require_ that all query params be considered (true)\n * or specify individual query params, ignoring anything not specified.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * <template>\n * <Link @href=\"/\" @includeActiveQueryParams={{true}} as |a|>\n * ...\n * </Link>\n * </template>\n * ```\n *\n * the data-active state here will only be \"true\" on\n * - `/`\n * - `/?foo=2`\n * - `/?foo=&bar=`\n *\n */\n includeActiveQueryParams?: true | string[];\n /**\n * When calculating the \"active\" state of the link, you may decide\n * whether or not you want to consider sub paths to be active when\n * child routes/urls are active.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * <template>\n * <Link @href=\"/forum/1\" @activeOnSubPaths={{true}} as |a|>\n * ...\n * </Link>\n * </template>\n * ```\n *\n * the data-active state here will be \"true\" on\n * - `/forum/1`\n * - `/forum/1/posts`\n * - `/forum/1/posts/comments`\n * - `/forum/1/*etc*`\n *\n * if `@activeOnSubPaths` is set to false or left off\n * the data-active state here will only be \"true\" on\n * - `/forum/1`\n *\n */\n activeOnSubPaths?: true;\n };\n Blocks: {\n default: [\n {\n /**\n * Indicates if the passed `href` is pointing to an external site.\n * Useful if you want your links to have additional context for when\n * a user is about to leave your site.\n *\n * For example:\n *\n * ```gjs live preview\n * import { Link } from 'ember-primitives';\n *\n * const MyLink = <template>\n * <Link @href={{@href}} as |a|>\n * {{yield}}\n * {{#if a.isExternal}}\n * ➚\n * {{/if}}\n * </Link>\n * </template>;\n *\n * <template>\n * <MyLink @href=\"https://developer.mozilla.org\">MDN</MyLink> \n * <MyLink @href=\"/\">Home</MyLink>\n * </template>\n * ```\n */\n isExternal: boolean;\n /**\n * Indicates if the passed `href` is *active*, or the user is on the same basepath.\n * This allows consumers to style their link if they wish or style their text.\n * The active state will also be present on a `data-active` attribute on the generated anchor tag.\n *\n *\n * For example\n * ```gjs\n * import { Link, service } from 'ember-primitives';\n *\n * const MyLink = <template>\n * <Link @href=\"...\"> as |a|>\n * <span class=\"{{if a.isActive 'underline'}}\">\n * {{yield}}\n * </span>\n * </Link>\n * </template>\n *\n * <template>\n * {{#let (service 'router') as |router|}}\n * <MyLink @href={{router.currentURL}}>Ths page</MyLink> \n * <MyLink @href=\"/\">Home</MyLink>\n * {{/let}}\n * </template>\n * ```\n *\n * By default, the query params are omitted from `isActive` calculation, but you may\n * configure the query params to be included if you wish\n * See: `@includeActiveQueryParams`\n *\n * By default, only the exact route/url is considered for the `isActive` calculation,\n * but you may configure sub routes/paths to also be considered active\n * See: `@activeOnSubPaths`\n *\n * Note that external links are never active.\n */\n isActive: boolean;\n },\n ];\n };\n}\n\n/**\n * 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.\n *\n *\n * [mdn-a]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a\n */\nexport const Link: TOC<Signature> = <template>\n {{#let (link @href includeActiveQueryParams=@includeActiveQueryParams activeOnSubPaths=@activeOnSubPaths) as |l|}}\n {{#if l.isExternal}}\n <ExternalLink href={{@href}} ...attributes>\n {{yield (hash isExternal=true isActive=false)}}\n </ExternalLink>\n {{else}}\n <a\n data-active={{l.isActive}}\n href={{if @href @href \"##missing##\"}}\n {{on \"click\" l.handleClick}}\n ...attributes\n >\n {{yield (hash isExternal=false isActive=l.isActive)}}\n </a>\n {{/if}}\n {{/let}}\n</template>;\n\nexport default Link;\n"],"names":["Link","setComponentTemplate","precompileTemplate","strictMode","scope","link","ExternalLink","hash","on","templateOnly"],"mappings":";;;;;;;;AAAA;;;;;AAKC;AA6ID;;;;;AAKC;MACYA,IAAU,GAAAC,oBAAA,CAAaC,kBAAA,CAiBpC,ogBAAA,EAAA;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAC,IAAA;IAAAC,YAAA;IAAAC,IAAA;AAAAC,IAAAA;AAAA,GAAA;AAAU,CAAE,CAAA,EAAAC,YAAA,EAAA;;;;"}
|
package/dist/components/menu.js
CHANGED
|
@@ -34,46 +34,46 @@ const Separator = setComponentTemplate(precompileTemplate("\n <div role=\"separ
|
|
|
34
34
|
* If we used `mouseOver`/`mouseEnter` it would not re-focus when the mouse
|
|
35
35
|
* wiggles. This is to match native menu implementation.
|
|
36
36
|
*/
|
|
37
|
-
function focusOnHover(
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
37
|
+
function focusOnHover(e) {
|
|
38
|
+
const item = e.currentTarget;
|
|
39
|
+
if (item instanceof HTMLElement) {
|
|
40
|
+
item?.focus();
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
const Item = setComponentTemplate(precompileTemplate("\n <button type=\"button\" role=\"menuitem\" {{!-- @glint-
|
|
43
|
+
const Item = setComponentTemplate(precompileTemplate("\n <button type=\"button\" role=\"menuitem\" {{!-- @glint-expect-error !--}} {{(if @onSelect (modifier on \"click\" @onSelect))}} {{on \"pointermove\" focusOnHover}} ...attributes>\n {{yield}}\n </button>\n", {
|
|
44
44
|
strictMode: true,
|
|
45
45
|
scope: () => ({
|
|
46
46
|
on,
|
|
47
47
|
focusOnHover
|
|
48
48
|
})
|
|
49
49
|
}), templateOnly());
|
|
50
|
-
const installContent = modifier((
|
|
51
|
-
isOpen
|
|
52
|
-
triggerElement
|
|
50
|
+
const installContent = modifier((element, _, {
|
|
51
|
+
isOpen,
|
|
52
|
+
triggerElement
|
|
53
53
|
}) => {
|
|
54
54
|
// focus first focusable element on the content
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
container:
|
|
55
|
+
const tabster = getTabster(window);
|
|
56
|
+
const firstFocusable = tabster?.focusable.findFirst({
|
|
57
|
+
container: element
|
|
58
58
|
});
|
|
59
|
-
|
|
59
|
+
firstFocusable?.focus();
|
|
60
60
|
// listen for "outside" clicks
|
|
61
|
-
function
|
|
62
|
-
if (
|
|
63
|
-
|
|
61
|
+
function onDocumentClick(e) {
|
|
62
|
+
if (isOpen.current && e.target && !element.contains(e.target) && !triggerElement.current?.contains(e.target)) {
|
|
63
|
+
isOpen.current = false;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
// listen for the escape key
|
|
67
|
-
function
|
|
68
|
-
if (
|
|
69
|
-
|
|
67
|
+
function onDocumentKeydown(e) {
|
|
68
|
+
if (isOpen.current && e.key === "Escape") {
|
|
69
|
+
isOpen.current = false;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
document.addEventListener(
|
|
73
|
-
document.addEventListener(
|
|
72
|
+
document.addEventListener("click", onDocumentClick);
|
|
73
|
+
document.addEventListener("keydown", onDocumentKeydown);
|
|
74
74
|
return () => {
|
|
75
|
-
document.removeEventListener(
|
|
76
|
-
document.removeEventListener(
|
|
75
|
+
document.removeEventListener("click", onDocumentClick);
|
|
76
|
+
document.removeEventListener("keydown", onDocumentKeydown);
|
|
77
77
|
};
|
|
78
78
|
});
|
|
79
79
|
const Content = setComponentTemplate(precompileTemplate("\n {{#if @isOpen.current}}\n <@PopoverContent id={{@contentId}} role=\"menu\" data-tabster={{TABSTER_CONFIG_CONTENT}} tabindex=\"0\" {{installContent isOpen=@isOpen triggerElement=@triggerElement}} {{on \"click\" @isOpen.toggle}} ...attributes>\n {{yield (hash Item=Item Separator=Separator)}}\n </@PopoverContent>\n {{/if}}\n", {
|
|
@@ -87,37 +87,38 @@ const Content = setComponentTemplate(precompileTemplate("\n {{#if @isOpen.curre
|
|
|
87
87
|
Separator
|
|
88
88
|
})
|
|
89
89
|
}), templateOnly());
|
|
90
|
-
const trigger = modifier((
|
|
91
|
-
triggerElement
|
|
92
|
-
isOpen
|
|
93
|
-
contentId
|
|
94
|
-
setReference
|
|
95
|
-
stopPropagation
|
|
96
|
-
preventDefault
|
|
90
|
+
const trigger = modifier((element, _, {
|
|
91
|
+
triggerElement,
|
|
92
|
+
isOpen,
|
|
93
|
+
contentId,
|
|
94
|
+
setReference,
|
|
95
|
+
stopPropagation,
|
|
96
|
+
preventDefault
|
|
97
97
|
}) => {
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
element.setAttribute("aria-haspopup", "menu");
|
|
99
|
+
if (isOpen.current) {
|
|
100
|
+
element.setAttribute("aria-controls", contentId);
|
|
101
|
+
element.setAttribute("aria-expanded", "true");
|
|
102
102
|
} else {
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
element.removeAttribute("aria-controls");
|
|
104
|
+
element.setAttribute("aria-expanded", "false");
|
|
105
105
|
}
|
|
106
|
-
setTabsterAttribute(
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
|
|
106
|
+
setTabsterAttribute(element, TABSTER_CONFIG_TRIGGER);
|
|
107
|
+
const onTriggerClick = event => {
|
|
108
|
+
if (stopPropagation) {
|
|
109
|
+
event.stopPropagation();
|
|
110
110
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
111
|
+
if (preventDefault) {
|
|
112
|
+
event.preventDefault();
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
isOpen.toggle();
|
|
115
115
|
};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
element.addEventListener("click", onTriggerClick);
|
|
117
|
+
triggerElement.current = element;
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
119
|
+
setReference(element);
|
|
119
120
|
return () => {
|
|
120
|
-
|
|
121
|
+
element.removeEventListener("click", onTriggerClick);
|
|
121
122
|
};
|
|
122
123
|
});
|
|
123
124
|
const Trigger = setComponentTemplate(precompileTemplate("\n <button type=\"button\" {{@triggerModifier stopPropagation=@stopPropagation preventDefault=@preventDefault}} ...attributes>\n {{yield}}\n </button>\n", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"menu.js","sources":["../../src/components/menu.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\nimport { hash } from \"@ember/helper\";\nimport { on } from \"@ember/modifier\";\nimport { guidFor } from \"@ember/object/internals\";\n\nimport { modifier as eModifier } from \"ember-modifier\";\nimport { cell } from \"ember-resources\";\nimport { getTabster, getTabsterAttribute, MoverDirections, setTabsterAttribute } from \"tabster\";\n\nimport { Popover, type Signature as PopoverSignature } from \"./popover.gts\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type { WithBoundArgs } from \"@glint/template\";\n\ntype Cell<V> = ReturnType<typeof cell<V>>;\ntype PopoverArgs = PopoverSignature[\"Args\"];\ntype PopoverBlockParams = PopoverSignature[\"Blocks\"][\"default\"][0];\n\nconst TABSTER_CONFIG_CONTENT = getTabsterAttribute(\n {\n mover: {\n direction: MoverDirections.Both,\n cyclic: true,\n },\n deloser: {},\n },\n true,\n);\n\nconst TABSTER_CONFIG_TRIGGER = {\n deloser: {},\n};\n\nexport interface Signature {\n Args: PopoverArgs;\n Blocks: {\n default: [\n {\n arrow: PopoverBlockParams[\"arrow\"];\n trigger: WithBoundArgs<\n typeof trigger,\n \"triggerElement\" | \"contentId\" | \"isOpen\" | \"setReference\"\n >;\n Trigger: WithBoundArgs<typeof Trigger, \"triggerModifier\">;\n Content: WithBoundArgs<\n typeof Content,\n \"triggerElement\" | \"contentId\" | \"isOpen\" | \"PopoverContent\"\n >;\n isOpen: boolean;\n },\n ];\n };\n}\n\nexport interface SeparatorSignature {\n Element: HTMLDivElement;\n Blocks: { default: [] };\n}\n\nconst Separator: TOC<SeparatorSignature> = <template>\n <div role=\"separator\" ...attributes>\n {{yield}}\n </div>\n</template>;\n\n/**\n * We focus items on `pointerMove` to achieve the following:\n *\n * - Mouse over an item (it focuses)\n * - Leave mouse where it is and use keyboard to focus a different item\n * - Wiggle mouse without it leaving previously focused item\n * - Previously focused item should re-focus\n *\n * If we used `mouseOver`/`mouseEnter` it would not re-focus when the mouse\n * wiggles. This is to match native menu implementation.\n */\nfunction focusOnHover(e: PointerEvent) {\n const item = e.currentTarget;\n\n if (item instanceof HTMLElement) {\n item?.focus();\n }\n}\n\nexport interface ItemSignature {\n Element: HTMLButtonElement;\n Args: { onSelect?: (event: Event) => void };\n Blocks: { default: [] };\n}\n\nconst Item: TOC<ItemSignature> = <template>\n <button\n type=\"button\"\n role=\"menuitem\"\n {{! @glint-expect-error !}}\n {{(if @onSelect (modifier on \"click\" @onSelect))}}\n {{on \"pointermove\" focusOnHover}}\n ...attributes\n >\n {{yield}}\n </button>\n</template>;\n\nconst installContent = eModifier<{\n Element: HTMLElement;\n Args: {\n Named: {\n isOpen: Cell<boolean>;\n triggerElement: Cell<HTMLElement>;\n };\n };\n}>((element, _: [], { isOpen, triggerElement }) => {\n // focus first focusable element on the content\n const tabster = getTabster(window);\n const firstFocusable = tabster?.focusable.findFirst({\n container: element,\n });\n\n firstFocusable?.focus();\n\n // listen for \"outside\" clicks\n function onDocumentClick(e: MouseEvent) {\n if (\n isOpen.current &&\n e.target &&\n !element.contains(e.target as HTMLElement) &&\n !triggerElement.current?.contains(e.target as HTMLElement)\n ) {\n isOpen.current = false;\n }\n }\n\n // listen for the escape key\n function onDocumentKeydown(e: KeyboardEvent) {\n if (isOpen.current && e.key === \"Escape\") {\n isOpen.current = false;\n }\n }\n\n document.addEventListener(\"click\", onDocumentClick);\n document.addEventListener(\"keydown\", onDocumentKeydown);\n\n return () => {\n document.removeEventListener(\"click\", onDocumentClick);\n document.removeEventListener(\"keydown\", onDocumentKeydown);\n };\n});\n\ninterface PrivateContentSignature {\n Element: HTMLDivElement;\n Args: {\n triggerElement: Cell<HTMLElement>;\n contentId: string;\n isOpen: Cell<boolean>;\n PopoverContent: PopoverBlockParams[\"Content\"];\n };\n Blocks: { default: [{ Item: typeof Item; Separator: typeof Separator }] };\n}\n\nexport interface ContentSignature {\n Element: PrivateContentSignature[\"Element\"];\n Blocks: PrivateContentSignature[\"Blocks\"];\n}\n\nconst Content: TOC<PrivateContentSignature> = <template>\n {{#if @isOpen.current}}\n <@PopoverContent\n id={{@contentId}}\n role=\"menu\"\n data-tabster={{TABSTER_CONFIG_CONTENT}}\n tabindex=\"0\"\n {{installContent isOpen=@isOpen triggerElement=@triggerElement}}\n {{on \"click\" @isOpen.toggle}}\n ...attributes\n >\n {{yield (hash Item=Item Separator=Separator)}}\n </@PopoverContent>\n {{/if}}\n</template>;\n\ninterface PrivateTriggerModifierSignature {\n Element: HTMLElement;\n Args: {\n Named: {\n triggerElement: Cell<HTMLElement>;\n isOpen: Cell<boolean>;\n contentId: string;\n setReference: PopoverBlockParams[\"setReference\"];\n stopPropagation?: boolean;\n preventDefault?: boolean;\n };\n };\n}\n\nexport interface TriggerModifierSignature {\n Element: PrivateTriggerModifierSignature[\"Element\"];\n}\n\nconst trigger = eModifier<PrivateTriggerModifierSignature>(\n (\n element,\n _: [],\n { triggerElement, isOpen, contentId, setReference, stopPropagation, preventDefault },\n ) => {\n element.setAttribute(\"aria-haspopup\", \"menu\");\n\n if (isOpen.current) {\n element.setAttribute(\"aria-controls\", contentId);\n element.setAttribute(\"aria-expanded\", \"true\");\n } else {\n element.removeAttribute(\"aria-controls\");\n element.setAttribute(\"aria-expanded\", \"false\");\n }\n\n setTabsterAttribute(element, TABSTER_CONFIG_TRIGGER);\n\n const onTriggerClick = (event: MouseEvent) => {\n if (stopPropagation) {\n event.stopPropagation();\n }\n\n if (preventDefault) {\n event.preventDefault();\n }\n\n isOpen.toggle();\n };\n\n element.addEventListener(\"click\", onTriggerClick);\n\n triggerElement.current = element;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n setReference(element);\n\n return () => {\n element.removeEventListener(\"click\", onTriggerClick);\n };\n },\n);\n\ninterface PrivateTriggerSignature {\n Element: HTMLButtonElement;\n Args: {\n triggerModifier: WithBoundArgs<\n typeof trigger,\n \"triggerElement\" | \"contentId\" | \"isOpen\" | \"setReference\"\n >;\n stopPropagation?: boolean;\n preventDefault?: boolean;\n };\n Blocks: { default: [] };\n}\n\nexport interface TriggerSignature {\n Element: PrivateTriggerSignature[\"Element\"];\n Blocks: PrivateTriggerSignature[\"Blocks\"];\n}\n\nconst Trigger: TOC<PrivateTriggerSignature> = <template>\n <button\n type=\"button\"\n {{@triggerModifier stopPropagation=@stopPropagation preventDefault=@preventDefault}}\n ...attributes\n >\n {{yield}}\n </button>\n</template>;\n\nconst IsOpen = () => cell<boolean>(false);\nconst TriggerElement = () => cell<HTMLElement>();\n\nexport class Menu extends Component<Signature> {\n contentId = guidFor(this);\n\n <template>\n {{#let (IsOpen) (TriggerElement) as |isOpen triggerEl|}}\n <Popover\n @flipOptions={{@flipOptions}}\n @middleware={{@middleware}}\n @offsetOptions={{@offsetOptions}}\n @placement={{@placement}}\n @shiftOptions={{@shiftOptions}}\n @strategy={{@strategy}}\n @inline={{@inline}}\n as |p|\n >\n {{#let\n (modifier\n trigger\n triggerElement=triggerEl\n isOpen=isOpen\n contentId=this.contentId\n setReference=p.setReference\n )\n as |triggerModifier|\n }}\n {{yield\n (hash\n trigger=triggerModifier\n Trigger=(component Trigger triggerModifier=triggerModifier)\n Content=(component\n Content\n PopoverContent=p.Content\n isOpen=isOpen\n triggerElement=triggerEl\n contentId=this.contentId\n )\n arrow=p.arrow\n isOpen=isOpen.current\n )\n }}\n {{/let}}\n </Popover>\n {{/let}}\n </template>\n}\n\nexport default Menu;\n"],"names":["TABSTER_CONFIG_CONTENT","getTabsterAttribute","mover","direction","MoverDirections","Both","cyclic","deloser","TABSTER_CONFIG_TRIGGER","Separator","setComponentTemplate","precompileTemplate","strictMode","templateOnly","focusOnHover","e","item","currentTarget","HTMLElement","focus","Item","scope","on","installContent","eModifier","element","_","isOpen","triggerElement","tabster","getTabster","window","firstFocusable","focusable","findFirst","container","onDocumentClick","current","target","contains","onDocumentKeydown","key","document","addEventListener","removeEventListener","Content","hash","trigger","contentId","setReference","stopPropagation","preventDefault","setAttribute","removeAttribute","setTabsterAttribute","onTriggerClick","event","toggle","Trigger","IsOpen","cell","TriggerElement","Menu","Component","guidFor","Popover"],"mappings":"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"}
|
|
@@ -4,11 +4,11 @@ import { precompileTemplate } from '@ember/template-compilation';
|
|
|
4
4
|
import { setComponentTemplate } from '@ember/component';
|
|
5
5
|
import templateOnly from '@ember/component/template-only';
|
|
6
6
|
|
|
7
|
-
const reset =
|
|
8
|
-
assert(
|
|
9
|
-
|
|
10
|
-
assert(
|
|
11
|
-
|
|
7
|
+
const reset = event => {
|
|
8
|
+
assert("[BUG]: reset called without an event.target", event.target instanceof HTMLElement);
|
|
9
|
+
const form = event.target.closest("form");
|
|
10
|
+
assert("Form is missing. Cannot use <Reset> without being contained within a <form>", form instanceof HTMLFormElement);
|
|
11
|
+
form.reset();
|
|
12
12
|
};
|
|
13
13
|
const Submit = setComponentTemplate(precompileTemplate("\n <button type=\"submit\" ...attributes>Submit</button>\n", {
|
|
14
14
|
strictMode: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buttons.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"buttons.js","sources":["../../../src/components/one-time-password/buttons.gts"],"sourcesContent":["import { assert } from \"@ember/debug\";\nimport { on } from \"@ember/modifier\";\n\nimport type { TOC } from \"@ember/component/template-only\";\n\nconst reset = (event: Event) => {\n assert(\"[BUG]: reset called without an event.target\", event.target instanceof HTMLElement);\n\n const form = event.target.closest(\"form\");\n\n assert(\n \"Form is missing. Cannot use <Reset> without being contained within a <form>\",\n form instanceof HTMLFormElement,\n );\n\n form.reset();\n};\n\nexport const Submit: TOC<{\n Element: HTMLButtonElement;\n Blocks: { default: [] };\n}> = <template>\n <button type=\"submit\" ...attributes>Submit</button>\n</template>;\n\nexport const Reset: TOC<{\n Element: HTMLButtonElement;\n Blocks: { default: [] };\n}> = <template>\n <button type=\"button\" {{on \"click\" reset}} ...attributes>{{yield}}</button>\n</template>;\n"],"names":["reset","event","assert","target","HTMLElement","form","closest","HTMLFormElement","Submit","setComponentTemplate","precompileTemplate","strictMode","templateOnly","Reset","scope","on"],"mappings":";;;;;;AAKA,MAAMA,KAAA,GAASC,KAAO,IAAA;EACpBC,MAAO,CAAA,6CAAA,EAA+CD,KAAM,CAAAE,MAAM,YAAYC,WAAA,CAAA;EAE9E,MAAMC,IAAO,GAAAJ,KAAA,CAAME,MAAM,CAACG,OAAO,CAAC,MAAA,CAAA;AAElCJ,EAAAA,MAAA,CACE,+EACAG,IAAgB,YAAAE,eAAA,CAAA;EAGlBF,IAAA,CAAKL,KAAK,EAAA;AACZ,CAAA;MAEaQ,MAGR,GAAAC,oBAAA,CAAAC,kBAAA,CAEL,6DAAA,EAAA;EAAAC,UAAA,EAAA;AAAU,CAAE,CAAA,EAAAC,YAAA,EAAA;MAECC,KAGR,GAAAJ,oBAAA,CAAAC,kBAAA,CAEL,uFAAA,EAAA;EAAAC,UAAA,EAAA,IAAA;AAAAG,EAAAA,KAAA,EAAAA,OAAA;IAAAC,EAAA;AAAAf,IAAAA;AAAA,GAAA;AAAU,CAAE,CAAA,EAAAY,YAAA,EAAA;;;;"}
|
|
@@ -3,19 +3,19 @@ import { warn } from '@ember/debug';
|
|
|
3
3
|
import { isDestroyed, isDestroying } from '@ember/destroyable';
|
|
4
4
|
import { on } from '@ember/modifier';
|
|
5
5
|
import { buildWaiter } from '@ember/test-waiters';
|
|
6
|
-
import {
|
|
6
|
+
import { handleNavigation, autoAdvance, handlePaste, selectAll, getCollectiveValue } from './utils.js';
|
|
7
7
|
import { precompileTemplate } from '@ember/template-compilation';
|
|
8
8
|
import { setComponentTemplate } from '@ember/component';
|
|
9
9
|
import templateOnly from '@ember/component/template-only';
|
|
10
10
|
|
|
11
11
|
const DEFAULT_LENGTH = 6;
|
|
12
|
-
function labelFor(
|
|
13
|
-
if (
|
|
14
|
-
return
|
|
12
|
+
function labelFor(inputIndex, labelFn) {
|
|
13
|
+
if (labelFn) {
|
|
14
|
+
return labelFn(inputIndex);
|
|
15
15
|
}
|
|
16
|
-
return `Please enter OTP character ${
|
|
16
|
+
return `Please enter OTP character ${inputIndex + 1}`;
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
const waiter = buildWaiter("ember-primitives:OTPInput:handleChange");
|
|
19
19
|
const Fields = setComponentTemplate(precompileTemplate("\n {{#each @fields as |_field i|}}\n <label>\n <span class=\"ember-primitives__sr-only\">{{labelFor i @labelFn}}</span>\n <input name=\"code{{i}}\" type=\"text\" inputmode=\"numeric\" autocomplete=\"off\" ...attributes {{on \"click\" selectAll}} {{on \"paste\" handlePaste}} {{on \"input\" autoAdvance}} {{on \"input\" @handleChange}} {{on \"keydown\" handleNavigation}} />\n </label>\n {{/each}}\n", {
|
|
20
20
|
strictMode: true,
|
|
21
21
|
scope: () => ({
|
|
@@ -33,7 +33,7 @@ class OTPInput extends Component {
|
|
|
33
33
|
* but only want to emit one change event if someone pastes
|
|
34
34
|
* multiple characters
|
|
35
35
|
*/
|
|
36
|
-
handleChange =
|
|
36
|
+
handleChange = event => {
|
|
37
37
|
if (!this.args.onChange) return;
|
|
38
38
|
if (!this.#token) {
|
|
39
39
|
this.#token = waiter.beginAsync();
|
|
@@ -51,17 +51,17 @@ class OTPInput extends Component {
|
|
|
51
51
|
waiter.endAsync(this.#token);
|
|
52
52
|
if (isDestroyed(this) || isDestroying(this)) return;
|
|
53
53
|
if (!this.args.onChange) return;
|
|
54
|
-
|
|
55
|
-
if (
|
|
54
|
+
const value = getCollectiveValue(event.target, this.length);
|
|
55
|
+
if (value === undefined) {
|
|
56
56
|
warn(`Value could not be determined for the OTP field. was it removed from the DOM?`, {
|
|
57
|
-
id:
|
|
57
|
+
id: "ember-primitives.OTPInput.missing-value"
|
|
58
58
|
});
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
this.args.onChange({
|
|
62
|
-
code:
|
|
63
|
-
complete:
|
|
64
|
-
},
|
|
62
|
+
code: value,
|
|
63
|
+
complete: value.length === this.length
|
|
64
|
+
}, event);
|
|
65
65
|
});
|
|
66
66
|
};
|
|
67
67
|
#token;
|
|
@@ -76,7 +76,7 @@ class OTPInput extends Component {
|
|
|
76
76
|
return new Array(this.length);
|
|
77
77
|
}
|
|
78
78
|
static {
|
|
79
|
-
setComponentTemplate(precompileTemplate("\n <fieldset ...attributes>\n {{#let (component Fields fields=this.fields handleChange=this.handleChange labelFn=@labelFn) as |CurriedFields|}}\n {{#if (has-block)}}\n {{yield CurriedFields}}\n {{else}}\n <CurriedFields />\n {{/if}}\n {{/let}}\n\n <style>\n .ember-primitives__sr-only {
|
|
79
|
+
setComponentTemplate(precompileTemplate("\n <fieldset ...attributes>\n {{#let (component Fields fields=this.fields handleChange=this.handleChange labelFn=@labelFn) as |CurriedFields|}}\n {{#if (has-block)}}\n {{yield CurriedFields}}\n {{else}}\n <CurriedFields />\n {{/if}}\n {{/let}}\n\n <style>\n .ember-primitives__sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n }\n </style>\n </fieldset>\n ", {
|
|
80
80
|
strictMode: true,
|
|
81
81
|
scope: () => ({
|
|
82
82
|
Fields
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"input.js","sources":["../../../src/components/one-time-password/input.gts"],"sourcesContent":["import Component from \"@glimmer/component\";\nimport { warn } from \"@ember/debug\";\nimport { isDestroyed, isDestroying } from \"@ember/destroyable\";\nimport { on } from \"@ember/modifier\";\nimport { buildWaiter } from \"@ember/test-waiters\";\n\nimport {\n autoAdvance,\n getCollectiveValue,\n handleNavigation,\n handlePaste,\n selectAll,\n} from \"./utils.ts\";\n\nimport type { TOC } from \"@ember/component/template-only\";\nimport type { WithBoundArgs } from \"@glint/template\";\n\nconst DEFAULT_LENGTH = 6;\n\nfunction labelFor(inputIndex: number, labelFn: undefined | ((index: number) => string)) {\n if (labelFn) {\n return labelFn(inputIndex);\n }\n\n return `Please enter OTP character ${inputIndex + 1}`;\n}\n\nconst waiter = buildWaiter(\"ember-primitives:OTPInput:handleChange\");\n\nconst Fields: TOC<{\n /**\n * Any attributes passed to this component will be applied to each input.\n */\n Element: HTMLInputElement;\n Args: {\n fields: unknown[];\n labelFn: (index: number) => string;\n handleChange: (event: Event) => void;\n };\n}> = <template>\n {{#each @fields as |_field i|}}\n <label>\n <span class=\"ember-primitives__sr-only\">{{labelFor i @labelFn}}</span>\n <input\n name=\"code{{i}}\"\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n ...attributes\n {{on \"click\" selectAll}}\n {{on \"paste\" handlePaste}}\n {{on \"input\" autoAdvance}}\n {{on \"input\" @handleChange}}\n {{on \"keydown\" handleNavigation}}\n />\n </label>\n {{/each}}\n</template>;\n\nexport class OTPInput extends Component<{\n /**\n * The collection of individual OTP inputs are contained by a fieldset.\n * Applying the `disabled` attribute to this fieldset will disable\n * all of the inputs, if that's desired.\n */\n Element: HTMLFieldSetElement;\n Args: {\n /**\n * How many characters the one-time-password field should be\n * Defaults to 6\n */\n length?: number;\n\n /**\n * To Customize the label of the input fields, you may pass a function.\n * By default, this is `Please enter OTP character ${index + 1}`.\n */\n labelFn?: (index: number) => string;\n\n /**\n * If passed, this function will be called when the <Input> changes.\n * All fields are considered one input.\n */\n onChange?: (\n data: {\n /**\n * The text from the collective `<Input>`\n *\n * `code` _may_ be shorter than `length`\n * if the user has not finished typing / pasting their code\n */\n code: string;\n /**\n * will be `true` if `code`'s length matches the passed `@length` or the default of 6\n */\n complete: boolean;\n },\n /**\n * The last input event received\n */\n event: Event,\n ) => void;\n };\n Blocks: {\n /**\n * Optionally, you may control how the Fields are rendered, with proceeding text,\n * additional attributes added, etc.\n *\n * This is how you can add custom validation to each input field.\n */\n default?: [fields: WithBoundArgs<typeof Fields, \"fields\" | \"handleChange\" | \"labelFn\">];\n };\n}> {\n /**\n * This is debounced, because we bind to each input,\n * but only want to emit one change event if someone pastes\n * multiple characters\n */\n handleChange = (event: Event) => {\n if (!this.args.onChange) return;\n\n if (!this.#token) {\n this.#token = waiter.beginAsync();\n }\n\n if (this.#frame) {\n cancelAnimationFrame(this.#frame);\n }\n\n // We use requestAnimationFrame to be friendly to rendering.\n // We don't know if onChange is going to want to cause paints\n // (it's also how we debounce, under the assumption that \"paste\" behavior\n // would be fast enough to be quicker than individual frames\n // (see logic in autoAdvance)\n // )\n this.#frame = requestAnimationFrame(() => {\n waiter.endAsync(this.#token);\n\n if (isDestroyed(this) || isDestroying(this)) return;\n if (!this.args.onChange) return;\n\n const value = getCollectiveValue(event.target, this.length);\n\n if (value === undefined) {\n warn(`Value could not be determined for the OTP field. was it removed from the DOM?`, {\n id: \"ember-primitives.OTPInput.missing-value\",\n });\n\n return;\n }\n\n this.args.onChange({ code: value, complete: value.length === this.length }, event);\n });\n };\n\n #token: unknown;\n #frame: number | undefined;\n\n get length() {\n return this.args.length ?? DEFAULT_LENGTH;\n }\n\n get fields() {\n // We only need to iterate a number of times,\n // so we don't care about the actual value or\n // referential integrity here\n return new Array<undefined>(this.length);\n }\n\n <template>\n <fieldset ...attributes>\n {{#let\n (component Fields fields=this.fields handleChange=this.handleChange labelFn=@labelFn)\n as |CurriedFields|\n }}\n {{#if (has-block)}}\n {{yield CurriedFields}}\n {{else}}\n <CurriedFields />\n {{/if}}\n {{/let}}\n\n <style>\n .ember-primitives__sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n }\n </style>\n </fieldset>\n </template>\n}\n"],"names":["DEFAULT_LENGTH","labelFor","inputIndex","labelFn","waiter","buildWaiter","Fields","setComponentTemplate","precompileTemplate","strictMode","scope","on","selectAll","handlePaste","autoAdvance","handleNavigation","templateOnly","OTPInput","Component","handleChange","event","args","onChange","beginAsync","cancelAnimationFrame","requestAnimationFrame","endAsync","isDestroyed","isDestroying","value","getCollectiveValue","target","length","undefined","warn","id","code","complete","fields","Array"],"mappings":";;;;;;;;;;AAiBA,MAAMA,cAAiB,GAAA,CAAA;AAEvB,SAASC,QAAAA,CAASC,UAAkB,EAAEC,OAAgD,EAAA;AACpF,EAAA,IAAIA,OAAS,EAAA;IACX,OAAOA,OAAQ,CAAAD,UAAA,CAAA;AACjB;AAEA,EAAA,OAAO,CAA8BA,2BAAAA,EAAAA,UAAA,GAAa,EAAG,CAAA;AACvD;AAEA,MAAME,SAASC,WAAY,CAAA,wCAAA,CAAA;AAE3B,MAAMC,MAUD,GAAAC,oBAAA,CAAAC,kBAAA,CAkBL,kaAAA,EAAA;EAAAC,UAAA,EAAA,IAAA;AAAAC,EAAAA,KAAA,EAAAA,OAAA;IAAAT,QAAA;IAAAU,EAAA;IAAAC,SAAA;IAAAC,WAAA;IAAAC,WAAA;AAAAC,IAAAA;AAAA,GAAA;AAAU,CAAA,CAAA,EAAAC,YAAA,EAAA,CAAA;AAEH,MAAMC,QAAiB,SAAAC,SAAA;AAsD5B;;;;;EAKAC,YAAA,GAAgBC,KAAO,IAAA;AACrB,IAAA,IAAI,CAAC,IAAI,CAACC,IAAI,CAACC,QAAQ,EAAE;AAEzB,IAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;MAChB,IAAI,CAAC,MAAM,GAAGlB,OAAOmB,UAAU,EAAA;AACjC;AAEA,IAAA,IAAI,IAAI,CAAC,MAAM,EAAE;AACfC,MAAAA,oBAAqB,CAAA,IAAI,CAAC,MAAM,CAAA;AAClC;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,IAAA,IAAI,CAAC,MAAM,GAAGC,qBAAsB,CAAA,MAAA;AAClCrB,MAAAA,MAAA,CAAOsB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAA;MAE3B,IAAIC,WAAY,CAAA,IAAI,CAAK,IAAAC,YAAA,CAAa,IAAI,CAAG,EAAA;AAC7C,MAAA,IAAI,CAAC,IAAI,CAACP,IAAI,CAACC,QAAQ,EAAE;MAEzB,MAAMO,QAAQC,kBAAmB,CAAAV,KAAA,CAAMW,MAAM,EAAE,IAAI,CAACC,MAAM,CAAA;MAE1D,IAAIH,UAAUI,SAAW,EAAA;QACvBC,IAAK,CAAA,+EAA+E,EAAE;AACpFC,UAAAA,EAAI,EAAA;AACN,SAAA,CAAA;AAEA,QAAA;AACF;AAEA,MAAA,IAAI,CAACd,IAAI,CAACC,QAAQ,CAAC;AAAEc,QAAAA,IAAM,EAAAP,KAAA;AAAOQ,QAAAA,QAAA,EAAUR,KAAM,CAAAG,MAAM,KAAK,IAAI,CAACA;OAAU,EAAAZ,KAAA,CAAA;AAC9E,KAAA,CAAA;GACA;AAEF,EAAA,MAAM;AACN,EAAA,MAAM;EAEN,IAAIY,MAASA,GAAA;AACX,IAAA,OAAO,IAAI,CAACX,IAAI,CAACW,MAAM,IAAIhC,cAAA;AAC7B;EAEA,IAAIsC,MAASA,GAAA;AACX;AACA;AACA;AACA,IAAA,OAAO,IAAIC,KAAM,CAAW,IAAI,CAACP,MAAM,CAAA;AACzC;AAEA,EAAA;IAAAzB,oBAAA,CAAAC,kBAAA,CA2BA,0oBAAA,EAAA;MAAAC,UAAA,EAAA,IAAA;AAAAC,MAAAA,KAAA,EAAAA,OAAA;AAAAJ,QAAAA;AAAA,OAAA;KAAU,CAAA,EAAV,IAAW,CAAA;AAAD;AACZ;;;;"}
|
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
import { assert } from '@ember/debug';
|
|
2
|
-
import {
|
|
2
|
+
import { hash, fn } from '@ember/helper';
|
|
3
3
|
import { on } from '@ember/modifier';
|
|
4
4
|
import { buildWaiter } from '@ember/test-waiters';
|
|
5
|
-
import {
|
|
5
|
+
import { Reset, Submit } from './buttons.js';
|
|
6
6
|
import { OTPInput } from './input.js';
|
|
7
7
|
import { precompileTemplate } from '@ember/template-compilation';
|
|
8
8
|
import { setComponentTemplate } from '@ember/component';
|
|
9
9
|
import templateOnly from '@ember/component/template-only';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
const handleFormSubmit = (
|
|
13
|
-
|
|
14
|
-
assert(
|
|
15
|
-
|
|
16
|
-
let
|
|
17
|
-
for (
|
|
18
|
-
if (
|
|
19
|
-
|
|
11
|
+
const waiter = buildWaiter("ember-primitives:OTP:handleAutoSubmitAttempt");
|
|
12
|
+
const handleFormSubmit = (submit, event) => {
|
|
13
|
+
event.preventDefault();
|
|
14
|
+
assert("[BUG]: handleFormSubmit was not attached to a form. Please open an issue.", event.currentTarget instanceof HTMLFormElement);
|
|
15
|
+
const formData = new FormData(event.currentTarget);
|
|
16
|
+
let code = "";
|
|
17
|
+
for (const [key, value] of formData.entries()) {
|
|
18
|
+
if (key.startsWith("code")) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-base-to-string
|
|
20
|
+
code += value;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
code
|
|
23
|
+
submit({
|
|
24
|
+
code
|
|
24
25
|
});
|
|
25
26
|
};
|
|
26
|
-
function handleChange(
|
|
27
|
-
if (!
|
|
28
|
-
if (!
|
|
29
|
-
assert(
|
|
30
|
-
const
|
|
31
|
-
assert(
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
waiter.endAsync(
|
|
35
|
-
|
|
27
|
+
function handleChange(autoSubmit, data, event) {
|
|
28
|
+
if (!autoSubmit) return;
|
|
29
|
+
if (!data.complete) return;
|
|
30
|
+
assert("[BUG]: event target is not a known element type", event.target instanceof HTMLElement || event.target instanceof SVGElement);
|
|
31
|
+
const form = event.target.closest("form");
|
|
32
|
+
assert("[BUG]: Cannot handle event when <OTP> Inputs are not rendered within their <form>", form);
|
|
33
|
+
const token = waiter.beginAsync();
|
|
34
|
+
const finished = () => {
|
|
35
|
+
waiter.endAsync(token);
|
|
36
|
+
form.removeEventListener("submit", finished);
|
|
36
37
|
};
|
|
37
|
-
|
|
38
|
+
form.addEventListener("submit", finished);
|
|
38
39
|
// NOTE: when calling .submit() the submit event handlers are not run
|
|
39
|
-
|
|
40
|
+
form.requestSubmit();
|
|
40
41
|
}
|
|
41
42
|
const OTP = setComponentTemplate(precompileTemplate("\n <form {{on \"submit\" (fn handleFormSubmit @onSubmit)}} ...attributes>\n {{yield (hash Input=(component OTPInput length=@length onChange=(if @autoSubmit (fn handleChange @autoSubmit))) Submit=Submit Reset=Reset)}}\n </form>\n", {
|
|
42
43
|
strictMode: true,
|