ember-primitives 0.43.1 → 0.45.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/declarations/components/heading.d.ts +14 -0
- package/declarations/components/heading.d.ts.map +1 -0
- package/declarations/components/one-time-password.d.ts +3 -0
- package/declarations/components/one-time-password.d.ts.map +1 -0
- package/declarations/components/shadowed.d.ts +33 -3
- package/declarations/components/shadowed.d.ts.map +1 -1
- package/declarations/index.d.ts +1 -1
- package/declarations/index.d.ts.map +1 -1
- package/declarations/test-support/dom.d.ts +14 -0
- package/declarations/test-support/dom.d.ts.map +1 -0
- package/declarations/test-support.d.ts +7 -0
- package/declarations/test-support.d.ts.map +1 -0
- package/dist/{floating-ui/modifier.js → component-Bs3N-G9z.js} +72 -4
- package/dist/component-Bs3N-G9z.js.map +1 -0
- package/dist/components/accordion.js +60 -1
- package/dist/components/heading.js +109 -0
- package/dist/components/heading.js.map +1 -0
- package/dist/components/one-time-password.js +3 -0
- package/dist/components/one-time-password.js.map +1 -0
- package/dist/components/popover.js +1 -2
- package/dist/components/rating.js +1 -1
- package/dist/components/shadowed.js +47 -35
- package/dist/components/switch.js +8 -2
- package/dist/components/toggle.js +1 -1
- package/dist/components/zoetrope.js +1 -1
- package/dist/floating-ui.js +1 -2
- package/dist/index-D052JWRa.js +149 -0
- package/dist/index-D052JWRa.js.map +1 -0
- package/dist/{components/zoetrope/index.js → index-DKE67I8L.js} +3 -3
- package/dist/index-DKE67I8L.js.map +1 -0
- package/dist/index.js +3 -4
- package/dist/otp-C6hCCXKx.js +291 -0
- package/dist/otp-C6hCCXKx.js.map +1 -0
- package/dist/test-support.js +255 -0
- package/dist/test-support.js.map +1 -0
- package/dist/{components/-private/utils.js → utils-C5796IKA.js} +2 -2
- package/dist/utils-C5796IKA.js.map +1 -0
- package/package.json +1 -9
- package/declarations/components/one-time-password/index.d.ts +0 -3
- package/declarations/components/one-time-password/index.d.ts.map +0 -1
- package/declarations/test-support/index.d.ts +0 -6
- package/declarations/test-support/index.d.ts.map +0 -1
- package/dist/components/-private/typed-elements.js +0 -14
- package/dist/components/-private/typed-elements.js.map +0 -1
- package/dist/components/-private/utils.js.map +0 -1
- package/dist/components/accordion/content.js +0 -6
- package/dist/components/accordion/content.js.map +0 -1
- package/dist/components/accordion/header.js +0 -7
- package/dist/components/accordion/header.js.map +0 -1
- package/dist/components/accordion/item.js +0 -7
- package/dist/components/accordion/item.js.map +0 -1
- package/dist/components/accordion/public.js +0 -2
- package/dist/components/accordion/public.js.map +0 -1
- package/dist/components/accordion/trigger.js +0 -7
- package/dist/components/accordion/trigger.js.map +0 -1
- package/dist/components/one-time-password/buttons.js +0 -26
- package/dist/components/one-time-password/buttons.js.map +0 -1
- package/dist/components/one-time-password/index.js +0 -4
- package/dist/components/one-time-password/index.js.map +0 -1
- package/dist/components/one-time-password/input.js +0 -90
- package/dist/components/one-time-password/input.js.map +0 -1
- package/dist/components/one-time-password/otp.js +0 -58
- package/dist/components/one-time-password/otp.js.map +0 -1
- package/dist/components/one-time-password/utils.js +0 -144
- package/dist/components/one-time-password/utils.js.map +0 -1
- package/dist/components/rating/index.js +0 -45
- package/dist/components/rating/index.js.map +0 -1
- package/dist/components/rating/public-types.js +0 -2
- package/dist/components/rating/public-types.js.map +0 -1
- package/dist/components/rating/range.js +0 -15
- package/dist/components/rating/range.js.map +0 -1
- package/dist/components/rating/stars.js +0 -19
- package/dist/components/rating/stars.js.map +0 -1
- package/dist/components/rating/state.js +0 -82
- package/dist/components/rating/state.js.map +0 -1
- package/dist/components/rating/utils.js +0 -18
- package/dist/components/rating/utils.js.map +0 -1
- package/dist/components/zoetrope/index.js.map +0 -1
- package/dist/components/zoetrope/types.js +0 -2
- package/dist/components/zoetrope/types.js.map +0 -1
- package/dist/floating-ui/component.js +0 -63
- package/dist/floating-ui/component.js.map +0 -1
- package/dist/floating-ui/middleware.js +0 -15
- package/dist/floating-ui/middleware.js.map +0 -1
- package/dist/floating-ui/modifier.js.map +0 -1
- package/dist/item-CwIzoqlC.js +0 -68
- package/dist/item-CwIzoqlC.js.map +0 -1
- package/dist/test-support/a11y.js +0 -24
- package/dist/test-support/a11y.js.map +0 -1
- package/dist/test-support/index.js +0 -7
- package/dist/test-support/index.js.map +0 -1
- package/dist/test-support/otp.js +0 -34
- package/dist/test-support/otp.js.map +0 -1
- package/dist/test-support/rating.js +0 -92
- package/dist/test-support/rating.js.map +0 -1
- package/dist/test-support/routing.js +0 -54
- package/dist/test-support/routing.js.map +0 -1
- package/dist/test-support/zoetrope.js +0 -43
- package/dist/test-support/zoetrope.js.map +0 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
|
|
2
|
+
import Component from '@glimmer/component';
|
|
3
|
+
import { hash } from '@ember/helper';
|
|
4
|
+
import { on } from '@ember/modifier';
|
|
5
|
+
import { uniqueId } from './utils.js';
|
|
6
|
+
import { precompileTemplate } from '@ember/template-compilation';
|
|
7
|
+
import { setComponentTemplate } from '@ember/component';
|
|
8
|
+
import templateOnly from '@ember/component/template-only';
|
|
9
|
+
import { cached } from '@glimmer/tracking';
|
|
10
|
+
import { assert } from '@ember/debug';
|
|
11
|
+
import { localCopy } from 'tracked-toolbox';
|
|
12
|
+
import { g, i, n } from 'decorator-transforms/runtime';
|
|
13
|
+
|
|
14
|
+
const RatingRange = setComponentTemplate(precompileTemplate("\n <input ...attributes name={{@name}} type=\"range\" max={{@max}} value={{@value}} {{on \"change\" @handleChange}} />\n", {
|
|
15
|
+
strictMode: true,
|
|
16
|
+
scope: () => ({
|
|
17
|
+
on
|
|
18
|
+
})
|
|
19
|
+
}), templateOnly());
|
|
20
|
+
|
|
21
|
+
function isString(x) {
|
|
22
|
+
return typeof x === 'string';
|
|
23
|
+
}
|
|
24
|
+
function lte(a, b) {
|
|
25
|
+
return a <= b;
|
|
26
|
+
}
|
|
27
|
+
function percentSelected(a, b) {
|
|
28
|
+
const diff = b + 1 - a;
|
|
29
|
+
if (diff < 0) return 0;
|
|
30
|
+
if (diff > 1) return 100;
|
|
31
|
+
if (a === b) return 100;
|
|
32
|
+
const percent = diff * 100;
|
|
33
|
+
return percent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const Stars = setComponentTemplate(precompileTemplate("\n <div class=\"ember-primitives__rating__items\">\n {{#each @stars as |star|}}\n {{#let (uniqueId) as |id|}}\n <span class=\"ember-primitives__rating__item\" data-number={{star}} data-percent-selected={{percentSelected star @currentValue}} data-selected={{lte star @currentValue}} data-readonly={{@isReadonly}}>\n <label for=\"input-{{id}}\">\n <span visually-hidden>{{star}} star</span>\n <span aria-hidden=\"true\">\n {{#if (isString @icon)}}\n {{@icon}}\n {{else}}\n <@icon @value={{star}} @isSelected={{lte star @currentValue}} @percentSelected={{percentSelected star @currentValue}} @readonly={{@isReadonly}} />\n {{/if}}\n </span>\n </label>\n\n <input id=\"input-{{id}}\" type=\"radio\" name={{@name}} value={{star}} readonly={{@isReadonly}} checked={{lte star @currentValue}} />\n </span>\n {{/let}}\n {{/each}}\n </div>\n", {
|
|
37
|
+
strictMode: true,
|
|
38
|
+
scope: () => ({
|
|
39
|
+
uniqueId,
|
|
40
|
+
percentSelected,
|
|
41
|
+
lte,
|
|
42
|
+
isString
|
|
43
|
+
})
|
|
44
|
+
}), templateOnly());
|
|
45
|
+
|
|
46
|
+
class RatingState extends Component {
|
|
47
|
+
static {
|
|
48
|
+
g(this.prototype, "_value", [localCopy("args.value")]);
|
|
49
|
+
}
|
|
50
|
+
#_value = (i(this, "_value"), void 0); // eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
51
|
+
get value() {
|
|
52
|
+
return this._value ?? 0;
|
|
53
|
+
}
|
|
54
|
+
get stars() {
|
|
55
|
+
return Array.from({
|
|
56
|
+
length: this.args.max ?? 5
|
|
57
|
+
}, (_, index) => index + 1);
|
|
58
|
+
}
|
|
59
|
+
static {
|
|
60
|
+
n(this.prototype, "stars", [cached]);
|
|
61
|
+
}
|
|
62
|
+
setRating = value => {
|
|
63
|
+
if (this.args.readonly) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (value === this._value) {
|
|
67
|
+
this._value = 0;
|
|
68
|
+
} else {
|
|
69
|
+
this._value = value;
|
|
70
|
+
}
|
|
71
|
+
this.args.onChange?.(value);
|
|
72
|
+
};
|
|
73
|
+
setFromString = value => {
|
|
74
|
+
assert("[BUG]: value from input must be a string.", typeof value === "string");
|
|
75
|
+
const num = parseFloat(value);
|
|
76
|
+
if (isNaN(num)) {
|
|
77
|
+
// something went wrong.
|
|
78
|
+
// Since we're using event delegation,
|
|
79
|
+
// this could be from an unrelated input
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.setRating(num);
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Click events are captured by
|
|
86
|
+
* - radio changes (mouse and keyboard)
|
|
87
|
+
* - but only range clicks
|
|
88
|
+
*/
|
|
89
|
+
handleClick = event => {
|
|
90
|
+
// Since we're doing event delegation on a click, we want to make sure
|
|
91
|
+
// we don't do anything on other elements
|
|
92
|
+
const isValid = event.target instanceof HTMLInputElement && event.target.name === this.args.name && event.target.type === "radio";
|
|
93
|
+
if (!isValid) return;
|
|
94
|
+
const selected = event.target?.value;
|
|
95
|
+
this.setFromString(selected);
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Only attached to a range element, if present.
|
|
99
|
+
* Range elements don't fire click events on keyboard usage, like radios do
|
|
100
|
+
*/
|
|
101
|
+
handleChange = event => {
|
|
102
|
+
const isValid = event.target !== null && "value" in event.target;
|
|
103
|
+
if (!isValid) return;
|
|
104
|
+
this.setFromString(event.target.value);
|
|
105
|
+
};
|
|
106
|
+
static {
|
|
107
|
+
setComponentTemplate(precompileTemplate("\n {{yield (hash stars=this.stars total=this.stars.length handleClick=this.handleClick handleChange=this.handleChange setRating=this.setRating value=this.value) (hash total=this.stars.length value=this.value)}}\n ", {
|
|
108
|
+
strictMode: true,
|
|
109
|
+
scope: () => ({
|
|
110
|
+
hash
|
|
111
|
+
})
|
|
112
|
+
}), this);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class Rating extends Component {
|
|
117
|
+
name = `rating-${uniqueId()}`;
|
|
118
|
+
get icon() {
|
|
119
|
+
return this.args.icon ?? "★";
|
|
120
|
+
}
|
|
121
|
+
get isInteractive() {
|
|
122
|
+
return this.args.interactive ?? true;
|
|
123
|
+
}
|
|
124
|
+
get isChangeable() {
|
|
125
|
+
const readonly = this.args.readonly ?? false;
|
|
126
|
+
return !readonly && this.isInteractive;
|
|
127
|
+
}
|
|
128
|
+
get isReadonly() {
|
|
129
|
+
return !this.isChangeable;
|
|
130
|
+
}
|
|
131
|
+
get needsDescription() {
|
|
132
|
+
return !this.isInteractive;
|
|
133
|
+
}
|
|
134
|
+
static {
|
|
135
|
+
setComponentTemplate(precompileTemplate("\n <RatingState @max={{@max}} @value={{@value}} @name={{this.name}} @readonly={{this.isReadonly}} @onChange={{@onChange}} as |r publicState|>\n <fieldset class=\"ember-primitives__rating\" data-total={{r.total}} data-value={{r.value}} data-readonly={{this.isReadonly}} {{!-- We use event delegation, this isn't a primary interactive -- we're capturing events from inputs --}} {{!-- template-lint-disable no-invalid-interactive --}} {{on \"click\" r.handleClick}} ...attributes>\n {{#let (component Stars stars=r.stars icon=this.icon isReadonly=this.isReadonly name=this.name total=r.total currentValue=r.value) as |RatingStars|}}\n\n {{#if (has-block)}}\n {{yield (hash max=r.total total=r.total value=r.value name=this.name isReadonly=this.isReadonly isChangeable=this.isChangeable Stars=RatingStars Range=(component RatingRange max=r.total value=r.value name=this.name handleChange=r.handleChange))}}\n {{else}}\n {{#if this.needsDescription}}\n {{#if (has-block \"label\")}}\n {{yield publicState to=\"label\"}}\n {{else}}\n <span visually-hidden class=\"ember-primitives__rating__label\">Rated\n {{r.value}}\n out of\n {{r.total}}</span>\n {{/if}}\n {{else}}\n {{#if (has-block \"label\")}}\n <legend>\n {{yield publicState to=\"label\"}}\n </legend>\n {{/if}}\n {{/if}}\n\n <RatingStars />\n {{/if}}\n {{/let}}\n\n </fieldset>\n </RatingState>\n ", {
|
|
136
|
+
strictMode: true,
|
|
137
|
+
scope: () => ({
|
|
138
|
+
RatingState,
|
|
139
|
+
on,
|
|
140
|
+
Stars,
|
|
141
|
+
hash,
|
|
142
|
+
RatingRange
|
|
143
|
+
})
|
|
144
|
+
}), this);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { Rating as R };
|
|
149
|
+
//# sourceMappingURL=index-D052JWRa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-D052JWRa.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./styles.css"
|
|
1
|
+
import "./components/zoetrope/styles.css"
|
|
2
2
|
import Component from '@glimmer/component';
|
|
3
3
|
import { tracked } from '@glimmer/tracking';
|
|
4
4
|
import { hash } from '@ember/helper';
|
|
@@ -265,5 +265,5 @@ function getRelativeBoundingClientRect(childElement, parentElement) {
|
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
export { Zoetrope
|
|
269
|
-
//# sourceMappingURL=index.js.map
|
|
268
|
+
export { Zoetrope as Z };
|
|
269
|
+
//# sourceMappingURL=index-DKE67I8L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DKE67I8L.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/index.js
CHANGED
|
@@ -9,20 +9,19 @@ export { Key, KeyCombo } from './components/keys.js';
|
|
|
9
9
|
export { StickyFooter } from './components/layout/sticky-footer.js';
|
|
10
10
|
export { Link } from './components/link.js';
|
|
11
11
|
export { Menu } from './components/menu.js';
|
|
12
|
-
export { OTPInput } from './
|
|
13
|
-
export { OTP } from './components/one-time-password/otp.js';
|
|
12
|
+
export { a as OTP, O as OTPInput } from './otp-C6hCCXKx.js';
|
|
14
13
|
export { Popover } from './components/popover.js';
|
|
15
14
|
export { Portal } from './components/portal.js';
|
|
16
15
|
export { TARGETS as PORTALS, PortalTargets } from './components/portal-targets.js';
|
|
17
16
|
export { Progress } from './components/progress.js';
|
|
18
|
-
export { Rating } from './
|
|
17
|
+
export { R as Rating } from './index-D052JWRa.js';
|
|
19
18
|
export { Scroller } from './components/scroller.js';
|
|
20
19
|
export { Shadowed } from './components/shadowed.js';
|
|
21
20
|
export { Switch } from './components/switch.js';
|
|
22
21
|
export { Toggle } from './components/toggle.js';
|
|
23
22
|
export { ToggleGroup } from './components/toggle-group.js';
|
|
24
23
|
export { VisuallyHidden } from './components/visually-hidden.js';
|
|
25
|
-
export { Zoetrope } from './
|
|
24
|
+
export { Z as Zoetrope } from './index-DKE67I8L.js';
|
|
26
25
|
export { link } from './helpers/link.js';
|
|
27
26
|
export { service } from './helpers/service.js';
|
|
28
27
|
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
|
|
2
|
+
import { assert, warn } from '@ember/debug';
|
|
3
|
+
import { hash, fn } from '@ember/helper';
|
|
4
|
+
import { on } from '@ember/modifier';
|
|
5
|
+
import { buildWaiter } from '@ember/test-waiters';
|
|
6
|
+
import { precompileTemplate } from '@ember/template-compilation';
|
|
7
|
+
import { setComponentTemplate } from '@ember/component';
|
|
8
|
+
import templateOnly from '@ember/component/template-only';
|
|
9
|
+
import Component from '@glimmer/component';
|
|
10
|
+
import { isDestroyed, isDestroying } from '@ember/destroyable';
|
|
11
|
+
|
|
12
|
+
function getInputs(current) {
|
|
13
|
+
const fieldset = current.closest('fieldset');
|
|
14
|
+
assert('[BUG]: fieldset went missing', fieldset);
|
|
15
|
+
return [...fieldset.querySelectorAll('input')];
|
|
16
|
+
}
|
|
17
|
+
function nextInput(current) {
|
|
18
|
+
const inputs = getInputs(current);
|
|
19
|
+
const currentIndex = inputs.indexOf(current);
|
|
20
|
+
return inputs[currentIndex + 1];
|
|
21
|
+
}
|
|
22
|
+
function selectAll(event) {
|
|
23
|
+
const target = event.target;
|
|
24
|
+
assert(`selectAll is only meant for use with input elements`, target instanceof HTMLInputElement);
|
|
25
|
+
target.select();
|
|
26
|
+
}
|
|
27
|
+
function handlePaste(event) {
|
|
28
|
+
const target = event.target;
|
|
29
|
+
assert(`handlePaste is only meant for use with input elements`, target instanceof HTMLInputElement);
|
|
30
|
+
const clipboardData = event.clipboardData;
|
|
31
|
+
assert(`Could not get clipboardData while handling the paste event on OTP. Please report this issue on the ember-primitives repo with a reproduction. Thanks!`, clipboardData);
|
|
32
|
+
|
|
33
|
+
// This is typically not good to prevent paste.
|
|
34
|
+
// But because of the UX we're implementing,
|
|
35
|
+
// we want to split the pasted value across
|
|
36
|
+
// multiple text fields
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
const value = clipboardData.getData('Text');
|
|
39
|
+
const digits = value;
|
|
40
|
+
let i = 0;
|
|
41
|
+
let currElement = target;
|
|
42
|
+
while (currElement) {
|
|
43
|
+
currElement.value = digits[i++] || '';
|
|
44
|
+
const next = nextInput(currElement);
|
|
45
|
+
if (next instanceof HTMLInputElement) {
|
|
46
|
+
currElement = next;
|
|
47
|
+
} else {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// We want to select the first field again
|
|
53
|
+
// so that if someone holds paste, or
|
|
54
|
+
// pastes again, they get the same result.
|
|
55
|
+
target.select();
|
|
56
|
+
}
|
|
57
|
+
function handleNavigation(event) {
|
|
58
|
+
switch (event.key) {
|
|
59
|
+
case 'Backspace':
|
|
60
|
+
return handleBackspace(event);
|
|
61
|
+
case 'ArrowLeft':
|
|
62
|
+
return focusLeft(event);
|
|
63
|
+
case 'ArrowRight':
|
|
64
|
+
return focusRight(event);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function focusLeft(event) {
|
|
68
|
+
const target = event.target;
|
|
69
|
+
assert(`only allowed on input elements`, target instanceof HTMLInputElement);
|
|
70
|
+
const input = previousInput(target);
|
|
71
|
+
input?.focus();
|
|
72
|
+
requestAnimationFrame(() => {
|
|
73
|
+
input?.select();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function focusRight(event) {
|
|
77
|
+
const target = event.target;
|
|
78
|
+
assert(`only allowed on input elements`, target instanceof HTMLInputElement);
|
|
79
|
+
const input = nextInput(target);
|
|
80
|
+
input?.focus();
|
|
81
|
+
requestAnimationFrame(() => {
|
|
82
|
+
input?.select();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const syntheticEvent = new InputEvent('input');
|
|
86
|
+
function handleBackspace(event) {
|
|
87
|
+
if (event.key !== 'Backspace') return;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* We have to prevent default because we
|
|
91
|
+
* - want to clear the whole field
|
|
92
|
+
* - have the focus behavior keep up with the key-repeat
|
|
93
|
+
* speed of the user's computer
|
|
94
|
+
*/
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
const target = event.target;
|
|
97
|
+
if (target && 'value' in target) {
|
|
98
|
+
if (target.value === '') {
|
|
99
|
+
focusLeft({
|
|
100
|
+
target
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
target.value = '';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
target?.dispatchEvent(syntheticEvent);
|
|
107
|
+
}
|
|
108
|
+
function previousInput(current) {
|
|
109
|
+
const inputs = getInputs(current);
|
|
110
|
+
const currentIndex = inputs.indexOf(current);
|
|
111
|
+
return inputs[currentIndex - 1];
|
|
112
|
+
}
|
|
113
|
+
const autoAdvance = event => {
|
|
114
|
+
assert('[BUG]: autoAdvance called on non-input element', event.target instanceof HTMLInputElement);
|
|
115
|
+
const value = event.target.value;
|
|
116
|
+
if (value.length === 0) return;
|
|
117
|
+
if (value.length > 0) {
|
|
118
|
+
if ('data' in event && event.data && typeof event.data === 'string') {
|
|
119
|
+
event.target.value = event.data;
|
|
120
|
+
}
|
|
121
|
+
return focusRight(event);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
function getCollectiveValue(elementTarget, length) {
|
|
125
|
+
if (!elementTarget) return;
|
|
126
|
+
assert(`[BUG]: somehow the element target is not HTMLElement`, elementTarget instanceof HTMLElement);
|
|
127
|
+
let parent;
|
|
128
|
+
|
|
129
|
+
// TODO: should this logic be extracted?
|
|
130
|
+
// why is getting the target element within a shadow root hard?
|
|
131
|
+
if (!(elementTarget instanceof HTMLInputElement)) {
|
|
132
|
+
if (elementTarget.shadowRoot) {
|
|
133
|
+
parent = elementTarget.shadowRoot;
|
|
134
|
+
} else {
|
|
135
|
+
parent = elementTarget.closest('fieldset');
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
parent = elementTarget.closest('fieldset');
|
|
139
|
+
}
|
|
140
|
+
assert(`[BUG]: somehow the input fields were rendered without a parent element`, parent);
|
|
141
|
+
const elements = parent.querySelectorAll('input');
|
|
142
|
+
let value = '';
|
|
143
|
+
assert(`found elements (${elements.length}) do not match length (${length}). Was the same OTP input rendered more than once?`, elements.length === length);
|
|
144
|
+
for (const element of elements) {
|
|
145
|
+
assert('[BUG]: how did the queried elements become a non-input element?', element instanceof HTMLInputElement);
|
|
146
|
+
value += element.value;
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const DEFAULT_LENGTH = 6;
|
|
152
|
+
function labelFor(inputIndex, labelFn) {
|
|
153
|
+
if (labelFn) {
|
|
154
|
+
return labelFn(inputIndex);
|
|
155
|
+
}
|
|
156
|
+
return `Please enter OTP character ${inputIndex + 1}`;
|
|
157
|
+
}
|
|
158
|
+
const waiter$1 = buildWaiter("ember-primitives:OTPInput:handleChange");
|
|
159
|
+
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", {
|
|
160
|
+
strictMode: true,
|
|
161
|
+
scope: () => ({
|
|
162
|
+
labelFor,
|
|
163
|
+
on,
|
|
164
|
+
selectAll,
|
|
165
|
+
handlePaste,
|
|
166
|
+
autoAdvance,
|
|
167
|
+
handleNavigation
|
|
168
|
+
})
|
|
169
|
+
}), templateOnly());
|
|
170
|
+
class OTPInput extends Component {
|
|
171
|
+
/**
|
|
172
|
+
* This is debounced, because we bind to each input,
|
|
173
|
+
* but only want to emit one change event if someone pastes
|
|
174
|
+
* multiple characters
|
|
175
|
+
*/
|
|
176
|
+
handleChange = event => {
|
|
177
|
+
if (!this.args.onChange) return;
|
|
178
|
+
if (!this.#token) {
|
|
179
|
+
this.#token = waiter$1.beginAsync();
|
|
180
|
+
}
|
|
181
|
+
if (this.#frame) {
|
|
182
|
+
cancelAnimationFrame(this.#frame);
|
|
183
|
+
}
|
|
184
|
+
// We use requestAnimationFrame to be friendly to rendering.
|
|
185
|
+
// We don't know if onChange is going to want to cause paints
|
|
186
|
+
// (it's also how we debounce, under the assumption that "paste" behavior
|
|
187
|
+
// would be fast enough to be quicker than individual frames
|
|
188
|
+
// (see logic in autoAdvance)
|
|
189
|
+
// )
|
|
190
|
+
this.#frame = requestAnimationFrame(() => {
|
|
191
|
+
waiter$1.endAsync(this.#token);
|
|
192
|
+
if (isDestroyed(this) || isDestroying(this)) return;
|
|
193
|
+
if (!this.args.onChange) return;
|
|
194
|
+
const value = getCollectiveValue(event.target, this.length);
|
|
195
|
+
if (value === undefined) {
|
|
196
|
+
warn(`Value could not be determined for the OTP field. was it removed from the DOM?`, {
|
|
197
|
+
id: "ember-primitives.OTPInput.missing-value"
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.args.onChange({
|
|
202
|
+
code: value,
|
|
203
|
+
complete: value.length === this.length
|
|
204
|
+
}, event);
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
#token;
|
|
208
|
+
#frame;
|
|
209
|
+
get length() {
|
|
210
|
+
return this.args.length ?? DEFAULT_LENGTH;
|
|
211
|
+
}
|
|
212
|
+
get fields() {
|
|
213
|
+
// We only need to iterate a number of times,
|
|
214
|
+
// so we don't care about the actual value or
|
|
215
|
+
// referential integrity here
|
|
216
|
+
return new Array(this.length);
|
|
217
|
+
}
|
|
218
|
+
static {
|
|
219
|
+
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 ", {
|
|
220
|
+
strictMode: true,
|
|
221
|
+
scope: () => ({
|
|
222
|
+
Fields
|
|
223
|
+
})
|
|
224
|
+
}), this);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const reset = event => {
|
|
229
|
+
assert("[BUG]: reset called without an event.target", event.target instanceof HTMLElement);
|
|
230
|
+
const form = event.target.closest("form");
|
|
231
|
+
assert("Form is missing. Cannot use <Reset> without being contained within a <form>", form instanceof HTMLFormElement);
|
|
232
|
+
form.reset();
|
|
233
|
+
};
|
|
234
|
+
const Submit = setComponentTemplate(precompileTemplate("\n <button type=\"submit\" ...attributes>Submit</button>\n", {
|
|
235
|
+
strictMode: true
|
|
236
|
+
}), templateOnly());
|
|
237
|
+
const Reset = setComponentTemplate(precompileTemplate("\n <button type=\"button\" {{on \"click\" reset}} ...attributes>{{yield}}</button>\n", {
|
|
238
|
+
strictMode: true,
|
|
239
|
+
scope: () => ({
|
|
240
|
+
on,
|
|
241
|
+
reset
|
|
242
|
+
})
|
|
243
|
+
}), templateOnly());
|
|
244
|
+
|
|
245
|
+
const waiter = buildWaiter("ember-primitives:OTP:handleAutoSubmitAttempt");
|
|
246
|
+
const handleFormSubmit = (submit, event) => {
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
assert("[BUG]: handleFormSubmit was not attached to a form. Please open an issue.", event.currentTarget instanceof HTMLFormElement);
|
|
249
|
+
const formData = new FormData(event.currentTarget);
|
|
250
|
+
let code = "";
|
|
251
|
+
for (const [key, value] of formData.entries()) {
|
|
252
|
+
if (key.startsWith("code")) {
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-base-to-string
|
|
254
|
+
code += value;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
submit({
|
|
258
|
+
code
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
function handleChange(autoSubmit, data, event) {
|
|
262
|
+
if (!autoSubmit) return;
|
|
263
|
+
if (!data.complete) return;
|
|
264
|
+
assert("[BUG]: event target is not a known element type", event.target instanceof HTMLElement || event.target instanceof SVGElement);
|
|
265
|
+
const form = event.target.closest("form");
|
|
266
|
+
assert("[BUG]: Cannot handle event when <OTP> Inputs are not rendered within their <form>", form);
|
|
267
|
+
const token = waiter.beginAsync();
|
|
268
|
+
const finished = () => {
|
|
269
|
+
waiter.endAsync(token);
|
|
270
|
+
form.removeEventListener("submit", finished);
|
|
271
|
+
};
|
|
272
|
+
form.addEventListener("submit", finished);
|
|
273
|
+
// NOTE: when calling .submit() the submit event handlers are not run
|
|
274
|
+
form.requestSubmit();
|
|
275
|
+
}
|
|
276
|
+
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", {
|
|
277
|
+
strictMode: true,
|
|
278
|
+
scope: () => ({
|
|
279
|
+
on,
|
|
280
|
+
fn,
|
|
281
|
+
handleFormSubmit,
|
|
282
|
+
hash,
|
|
283
|
+
OTPInput,
|
|
284
|
+
handleChange,
|
|
285
|
+
Submit,
|
|
286
|
+
Reset
|
|
287
|
+
})
|
|
288
|
+
}), templateOnly());
|
|
289
|
+
|
|
290
|
+
export { OTPInput as O, OTP as a };
|
|
291
|
+
//# sourceMappingURL=otp-C6hCCXKx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otp-C6hCCXKx.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|