UNPKG

8.49 kBJavaScriptView Raw
1'use strict';
2
3/**
4 * Waits for a component to be ready for
5 * both custom element and non-custom element builds.
6 * If non-custom element build, el.componentOnReady
7 * will be used.
8 * For custom element builds, we wait a frame
9 * so that the inner contents of the component
10 * have a chance to render.
11 *
12 * Use this utility rather than calling
13 * el.componentOnReady yourself.
14 */
15const componentOnReady = (el, callback) => {
16 if (el.componentOnReady) {
17 el.componentOnReady().then((resolvedEl) => callback(resolvedEl));
18 }
19 else {
20 raf(() => callback(el));
21 }
22};
23/**
24 * Elements inside of web components sometimes need to inherit global attributes
25 * set on the host. For example, the inner input in `ion-input` should inherit
26 * the `title` attribute that developers set directly on `ion-input`. This
27 * helper function should be called in componentWillLoad and assigned to a variable
28 * that is later used in the render function.
29 *
30 * This does not need to be reactive as changing attributes on the host element
31 * does not trigger a re-render.
32 */
33const inheritAttributes = (el, attributes = []) => {
34 const attributeObject = {};
35 attributes.forEach(attr => {
36 if (el.hasAttribute(attr)) {
37 const value = el.getAttribute(attr);
38 if (value !== null) {
39 attributeObject[attr] = el.getAttribute(attr);
40 }
41 el.removeAttribute(attr);
42 }
43 });
44 return attributeObject;
45};
46const addEventListener = (el, eventName, callback, opts) => {
47 if (typeof window !== 'undefined') {
48 const win = window;
49 const config = win && win.Ionic && win.Ionic.config;
50 if (config) {
51 const ael = config.get('_ael');
52 if (ael) {
53 return ael(el, eventName, callback, opts);
54 }
55 else if (config._ael) {
56 return config._ael(el, eventName, callback, opts);
57 }
58 }
59 }
60 return el.addEventListener(eventName, callback, opts);
61};
62const removeEventListener = (el, eventName, callback, opts) => {
63 if (typeof window !== 'undefined') {
64 const win = window;
65 const config = win && win.Ionic && win.Ionic.config;
66 if (config) {
67 const rel = config.get('_rel');
68 if (rel) {
69 return rel(el, eventName, callback, opts);
70 }
71 else if (config._rel) {
72 return config._rel(el, eventName, callback, opts);
73 }
74 }
75 }
76 return el.removeEventListener(eventName, callback, opts);
77};
78/**
79 * Gets the root context of a shadow dom element
80 * On newer browsers this will be the shadowRoot,
81 * but for older browser this may just be the
82 * element itself.
83 *
84 * Useful for whenever you need to explicitly
85 * do "myElement.shadowRoot!.querySelector(...)".
86 */
87const getElementRoot = (el, fallback = el) => {
88 return el.shadowRoot || fallback;
89};
90/**
91 * Patched version of requestAnimationFrame that avoids ngzone
92 * Use only when you know ngzone should not run
93 */
94const raf = (h) => {
95 if (typeof __zone_symbol__requestAnimationFrame === 'function') {
96 return __zone_symbol__requestAnimationFrame(h);
97 }
98 if (typeof requestAnimationFrame === 'function') {
99 return requestAnimationFrame(h);
100 }
101 return setTimeout(h);
102};
103const hasShadowDom = (el) => {
104 return !!el.shadowRoot && !!el.attachShadow;
105};
106const findItemLabel = (componentEl) => {
107 const itemEl = componentEl.closest('ion-item');
108 if (itemEl) {
109 return itemEl.querySelector('ion-label');
110 }
111 return null;
112};
113/**
114 * This method is used for Ionic's input components that use Shadow DOM. In
115 * order to properly label the inputs to work with screen readers, we need
116 * to get the text content of the label outside of the shadow root and pass
117 * it to the input inside of the shadow root.
118 *
119 * Referencing label elements by id from outside of the component is
120 * impossible due to the shadow boundary, read more here:
121 * https://developer.salesforce.com/blogs/2020/01/accessibility-for-web-components.html
122 *
123 * @param componentEl The shadow element that needs the aria label
124 * @param inputId The unique identifier for the input
125 */
126const getAriaLabel = (componentEl, inputId) => {
127 let labelText;
128 // If the user provides their own label via the aria-labelledby attr
129 // we should use that instead of looking for an ion-label
130 const labelledBy = componentEl.getAttribute('aria-labelledby');
131 // Grab the id off of the component in case they are using
132 // a custom label using the label element
133 const componentId = componentEl.id;
134 let labelId = labelledBy !== null && labelledBy.trim() !== ''
135 ? labelledBy
136 : inputId + '-lbl';
137 let label = labelledBy !== null && labelledBy.trim() !== ''
138 ? document.getElementById(labelledBy)
139 : findItemLabel(componentEl);
140 if (label) {
141 if (labelledBy === null) {
142 label.id = labelId;
143 }
144 labelText = label.textContent;
145 label.setAttribute('aria-hidden', 'true');
146 // if there is no label, check to see if the user has provided
147 // one by setting an id on the component and using the label element
148 }
149 else if (componentId.trim() !== '') {
150 label = document.querySelector(`label[for="${componentId}"]`);
151 if (label) {
152 if (label.id !== '') {
153 labelId = label.id;
154 }
155 else {
156 label.id = labelId = `${componentId}-lbl`;
157 }
158 labelText = label.textContent;
159 }
160 }
161 return { label, labelId, labelText };
162};
163/**
164 * This method is used to add a hidden input to a host element that contains
165 * a Shadow DOM. It does not add the input inside of the Shadow root which
166 * allows it to be picked up inside of forms. It should contain the same
167 * values as the host element.
168 *
169 * @param always Add a hidden input even if the container does not use Shadow
170 * @param container The element where the input will be added
171 * @param name The name of the input
172 * @param value The value of the input
173 * @param disabled If true, the input is disabled
174 */
175const renderHiddenInput = (always, container, name, value, disabled) => {
176 if (always || hasShadowDom(container)) {
177 let input = container.querySelector('input.aux-input');
178 if (!input) {
179 input = container.ownerDocument.createElement('input');
180 input.type = 'hidden';
181 input.classList.add('aux-input');
182 container.appendChild(input);
183 }
184 input.disabled = disabled;
185 input.name = name;
186 input.value = value || '';
187 }
188};
189const clamp = (min, n, max) => {
190 return Math.max(min, Math.min(n, max));
191};
192const assert = (actual, reason) => {
193 if (!actual) {
194 const message = 'ASSERT: ' + reason;
195 console.error(message);
196 debugger; // tslint:disable-line
197 throw new Error(message);
198 }
199};
200const now = (ev) => {
201 return ev.timeStamp || Date.now();
202};
203const pointerCoord = (ev) => {
204 // get X coordinates for either a mouse click
205 // or a touch depending on the given event
206 if (ev) {
207 const changedTouches = ev.changedTouches;
208 if (changedTouches && changedTouches.length > 0) {
209 const touch = changedTouches[0];
210 return { x: touch.clientX, y: touch.clientY };
211 }
212 if (ev.pageX !== undefined) {
213 return { x: ev.pageX, y: ev.pageY };
214 }
215 }
216 return { x: 0, y: 0 };
217};
218/**
219 * @hidden
220 * Given a side, return if it should be on the end
221 * based on the value of dir
222 * @param side the side
223 * @param isRTL whether the application dir is rtl
224 */
225const isEndSide = (side) => {
226 const isRTL = document.dir === 'rtl';
227 switch (side) {
228 case 'start': return isRTL;
229 case 'end': return !isRTL;
230 default:
231 throw new Error(`"${side}" is not a valid value for [side]. Use "start" or "end" instead.`);
232 }
233};
234const debounceEvent = (event, wait) => {
235 const original = event._original || event;
236 return {
237 _original: event,
238 emit: debounce(original.emit.bind(original), wait)
239 };
240};
241const debounce = (func, wait = 0) => {
242 let timer;
243 return (...args) => {
244 clearTimeout(timer);
245 timer = setTimeout(func, wait, ...args);
246 };
247};
248
249exports.addEventListener = addEventListener;
250exports.assert = assert;
251exports.clamp = clamp;
252exports.componentOnReady = componentOnReady;
253exports.debounce = debounce;
254exports.debounceEvent = debounceEvent;
255exports.findItemLabel = findItemLabel;
256exports.getAriaLabel = getAriaLabel;
257exports.getElementRoot = getElementRoot;
258exports.hasShadowDom = hasShadowDom;
259exports.inheritAttributes = inheritAttributes;
260exports.isEndSide = isEndSide;
261exports.now = now;
262exports.pointerCoord = pointerCoord;
263exports.raf = raf;
264exports.removeEventListener = removeEventListener;
265exports.renderHiddenInput = renderHiddenInput;