UNPKG

21.7 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5const index = require('./index-a0a08b2a.js');
6const ionicGlobal = require('./ionic-global-06f21c1a.js');
7const helpers = require('./helpers-d381ec4d.js');
8const overlays = require('./overlays-59863ad4.js');
9const theme = require('./theme-30b7a575.js');
10require('./hardware-back-button-148ce546.js');
11
12const watchForOptions = (containerEl, tagName, onChange) => {
13 /* tslint:disable-next-line */
14 if (typeof MutationObserver === 'undefined') {
15 return;
16 }
17 const mutation = new MutationObserver(mutationList => {
18 onChange(getSelectedOption(mutationList, tagName));
19 });
20 mutation.observe(containerEl, {
21 childList: true,
22 subtree: true
23 });
24 return mutation;
25};
26const getSelectedOption = (mutationList, tagName) => {
27 let newOption;
28 mutationList.forEach(mut => {
29 // tslint:disable-next-line: prefer-for-of
30 for (let i = 0; i < mut.addedNodes.length; i++) {
31 newOption = findCheckedOption(mut.addedNodes[i], tagName) || newOption;
32 }
33 });
34 return newOption;
35};
36const findCheckedOption = (el, tagName) => {
37 if (el.nodeType !== 1) {
38 return undefined;
39 }
40 const options = (el.tagName === tagName.toUpperCase())
41 ? [el]
42 : Array.from(el.querySelectorAll(tagName));
43 return options.find((o) => o.value === el.value);
44};
45
46const selectIosCss = ":host{--placeholder-color:currentColor;--placeholder-opacity:0.33;padding-left:var(--padding-start);padding-right:var(--padding-end);padding-top:var(--padding-top);padding-bottom:var(--padding-bottom);display:-ms-flexbox;display:flex;position:relative;-ms-flex-align:center;align-items:center;font-family:var(--ion-font-family, inherit);overflow:hidden;z-index:2}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){:host{padding-left:unset;padding-right:unset;-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end)}}:host(.in-item){position:static;max-width:45%}:host(.select-disabled){opacity:0.4;pointer-events:none}:host(.ion-focused) button{border:2px solid #5e9ed6}.select-placeholder{color:var(--placeholder-color);opacity:var(--placeholder-opacity)}label{left:0;top:0;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;position:absolute;width:100%;height:100%;border:0;background:transparent;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;outline:none;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;opacity:0}[dir=rtl] label,:host-context([dir=rtl]) label{left:unset;right:unset;right:0}label::-moz-focus-inner{border:0}button{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;margin:0;padding:0;border:0;outline:0;clip:rect(0 0 0 0);opacity:0;overflow:hidden;-webkit-appearance:none;-moz-appearance:none}.select-icon{position:relative;opacity:0.33}.select-text{-ms-flex:1;flex:1;min-width:16px;font-size:inherit;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.select-icon-inner{left:5px;top:50%;margin-top:-2px;position:absolute;width:0;height:0;border-top:5px solid;border-right:5px solid transparent;border-left:5px solid transparent;color:currentColor;pointer-events:none}[dir=rtl] .select-icon-inner,:host-context([dir=rtl]) .select-icon-inner{left:unset;right:unset;right:5px}:host{--padding-top:10px;--padding-end:10px;--padding-bottom:10px;--padding-start:20px}.select-icon{width:12px;height:18px}";
47
48const selectMdCss = ":host{--placeholder-color:currentColor;--placeholder-opacity:0.33;padding-left:var(--padding-start);padding-right:var(--padding-end);padding-top:var(--padding-top);padding-bottom:var(--padding-bottom);display:-ms-flexbox;display:flex;position:relative;-ms-flex-align:center;align-items:center;font-family:var(--ion-font-family, inherit);overflow:hidden;z-index:2}@supports ((-webkit-margin-start: 0) or (margin-inline-start: 0)) or (-webkit-margin-start: 0){:host{padding-left:unset;padding-right:unset;-webkit-padding-start:var(--padding-start);padding-inline-start:var(--padding-start);-webkit-padding-end:var(--padding-end);padding-inline-end:var(--padding-end)}}:host(.in-item){position:static;max-width:45%}:host(.select-disabled){opacity:0.4;pointer-events:none}:host(.ion-focused) button{border:2px solid #5e9ed6}.select-placeholder{color:var(--placeholder-color);opacity:var(--placeholder-opacity)}label{left:0;top:0;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0;position:absolute;width:100%;height:100%;border:0;background:transparent;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;outline:none;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;opacity:0}[dir=rtl] label,:host-context([dir=rtl]) label{left:unset;right:unset;right:0}label::-moz-focus-inner{border:0}button{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;margin:0;padding:0;border:0;outline:0;clip:rect(0 0 0 0);opacity:0;overflow:hidden;-webkit-appearance:none;-moz-appearance:none}.select-icon{position:relative;opacity:0.33}.select-text{-ms-flex:1;flex:1;min-width:16px;font-size:inherit;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.select-icon-inner{left:5px;top:50%;margin-top:-2px;position:absolute;width:0;height:0;border-top:5px solid;border-right:5px solid transparent;border-left:5px solid transparent;color:currentColor;pointer-events:none}[dir=rtl] .select-icon-inner,:host-context([dir=rtl]) .select-icon-inner{left:unset;right:unset;right:5px}:host{--padding-top:10px;--padding-end:0;--padding-bottom:10px;--padding-start:16px}.select-icon{width:19px;height:19px}:host-context(.item-label-floating) .select-icon{-webkit-transform:translate3d(0, -9px, 0);transform:translate3d(0, -9px, 0)}";
49
50const Select = class {
51 constructor(hostRef) {
52 index.registerInstance(this, hostRef);
53 this.ionChange = index.createEvent(this, "ionChange", 7);
54 this.ionCancel = index.createEvent(this, "ionCancel", 7);
55 this.ionFocus = index.createEvent(this, "ionFocus", 7);
56 this.ionBlur = index.createEvent(this, "ionBlur", 7);
57 this.ionStyle = index.createEvent(this, "ionStyle", 7);
58 this.inputId = `ion-sel-${selectIds++}`;
59 this.didInit = false;
60 this.isExpanded = false;
61 /**
62 * If `true`, the user cannot interact with the select.
63 */
64 this.disabled = false;
65 /**
66 * The text to display on the cancel button.
67 */
68 this.cancelText = 'Cancel';
69 /**
70 * The text to display on the ok button.
71 */
72 this.okText = 'OK';
73 /**
74 * The name of the control, which is submitted with the form data.
75 */
76 this.name = this.inputId;
77 /**
78 * If `true`, the select can accept multiple values.
79 */
80 this.multiple = false;
81 /**
82 * The interface the select should use: `action-sheet`, `popover` or `alert`.
83 */
84 this.interface = 'alert';
85 /**
86 * Any additional options that the `alert`, `action-sheet` or `popover` interface
87 * can take. See the [ion-alert docs](../alert), the
88 * [ion-action-sheet docs](../action-sheet) and the
89 * [ion-popover docs](../popover) for the
90 * create options for each interface.
91 *
92 * Note: `interfaceOptions` will not override `inputs` or `buttons` with the `alert` interface.
93 */
94 this.interfaceOptions = {};
95 this.onClick = (ev) => {
96 this.setFocus();
97 this.open(ev);
98 };
99 this.onFocus = () => {
100 this.ionFocus.emit();
101 };
102 this.onBlur = () => {
103 this.ionBlur.emit();
104 };
105 }
106 disabledChanged() {
107 this.emitStyle();
108 }
109 valueChanged() {
110 this.emitStyle();
111 if (this.didInit) {
112 this.ionChange.emit({
113 value: this.value,
114 });
115 }
116 }
117 async connectedCallback() {
118 this.updateOverlayOptions();
119 this.emitStyle();
120 this.mutationO = watchForOptions(this.el, 'ion-select-option', async () => {
121 this.updateOverlayOptions();
122 });
123 }
124 disconnectedCallback() {
125 if (this.mutationO) {
126 this.mutationO.disconnect();
127 this.mutationO = undefined;
128 }
129 }
130 componentDidLoad() {
131 this.didInit = true;
132 }
133 /**
134 * Open the select overlay. The overlay is either an alert, action sheet, or popover,
135 * depending on the `interface` property on the `ion-select`.
136 *
137 * @param event The user interface event that called the open.
138 */
139 async open(event) {
140 if (this.disabled || this.isExpanded) {
141 return undefined;
142 }
143 const overlay = this.overlay = await this.createOverlay(event);
144 this.isExpanded = true;
145 overlay.onDidDismiss().then(() => {
146 this.overlay = undefined;
147 this.isExpanded = false;
148 this.setFocus();
149 });
150 await overlay.present();
151 return overlay;
152 }
153 createOverlay(ev) {
154 let selectInterface = this.interface;
155 if ((selectInterface === 'action-sheet' || selectInterface === 'popover') && this.multiple) {
156 console.warn(`Select interface cannot be "${selectInterface}" with a multi-value select. Using the "alert" interface instead.`);
157 selectInterface = 'alert';
158 }
159 if (selectInterface === 'popover' && !ev) {
160 console.warn('Select interface cannot be a "popover" without passing an event. Using the "alert" interface instead.');
161 selectInterface = 'alert';
162 }
163 if (selectInterface === 'popover') {
164 return this.openPopover(ev);
165 }
166 if (selectInterface === 'action-sheet') {
167 return this.openActionSheet();
168 }
169 return this.openAlert();
170 }
171 updateOverlayOptions() {
172 const overlay = this.overlay;
173 if (!overlay) {
174 return;
175 }
176 const childOpts = this.childOpts;
177 const value = this.value;
178 switch (this.interface) {
179 case 'action-sheet':
180 overlay.buttons = this.createActionSheetButtons(childOpts, value);
181 break;
182 case 'popover':
183 const popover = overlay.querySelector('ion-select-popover');
184 if (popover) {
185 popover.options = this.createPopoverOptions(childOpts, value);
186 }
187 break;
188 case 'alert':
189 const inputType = (this.multiple ? 'checkbox' : 'radio');
190 overlay.inputs = this.createAlertInputs(childOpts, inputType, value);
191 break;
192 }
193 }
194 createActionSheetButtons(data, selectValue) {
195 const actionSheetButtons = data.map(option => {
196 const value = getOptionValue(option);
197 // Remove hydrated before copying over classes
198 const copyClasses = Array.from(option.classList).filter(cls => cls !== 'hydrated').join(' ');
199 const optClass = `${OPTION_CLASS} ${copyClasses}`;
200 return {
201 role: (isOptionSelected(value, selectValue, this.compareWith) ? 'selected' : ''),
202 text: option.textContent,
203 cssClass: optClass,
204 handler: () => {
205 this.value = value;
206 }
207 };
208 });
209 // Add "cancel" button
210 actionSheetButtons.push({
211 text: this.cancelText,
212 role: 'cancel',
213 handler: () => {
214 this.ionCancel.emit();
215 }
216 });
217 return actionSheetButtons;
218 }
219 createAlertInputs(data, inputType, selectValue) {
220 const alertInputs = data.map(option => {
221 const value = getOptionValue(option);
222 // Remove hydrated before copying over classes
223 const copyClasses = Array.from(option.classList).filter(cls => cls !== 'hydrated').join(' ');
224 const optClass = `${OPTION_CLASS} ${copyClasses}`;
225 return {
226 type: inputType,
227 cssClass: optClass,
228 label: option.textContent || '',
229 value,
230 checked: isOptionSelected(value, selectValue, this.compareWith),
231 disabled: option.disabled
232 };
233 });
234 return alertInputs;
235 }
236 createPopoverOptions(data, selectValue) {
237 const popoverOptions = data.map(option => {
238 const value = getOptionValue(option);
239 // Remove hydrated before copying over classes
240 const copyClasses = Array.from(option.classList).filter(cls => cls !== 'hydrated').join(' ');
241 const optClass = `${OPTION_CLASS} ${copyClasses}`;
242 return {
243 text: option.textContent || '',
244 cssClass: optClass,
245 value,
246 checked: isOptionSelected(value, selectValue, this.compareWith),
247 disabled: option.disabled,
248 handler: () => {
249 this.value = value;
250 this.close();
251 }
252 };
253 });
254 return popoverOptions;
255 }
256 async openPopover(ev) {
257 const interfaceOptions = this.interfaceOptions;
258 const mode = ionicGlobal.getIonMode(this);
259 const value = this.value;
260 const popoverOpts = Object.assign(Object.assign({ mode }, interfaceOptions), { component: 'ion-select-popover', cssClass: ['select-popover', interfaceOptions.cssClass], event: ev, componentProps: {
261 header: interfaceOptions.header,
262 subHeader: interfaceOptions.subHeader,
263 message: interfaceOptions.message,
264 value,
265 options: this.createPopoverOptions(this.childOpts, value)
266 } });
267 return overlays.popoverController.create(popoverOpts);
268 }
269 async openActionSheet() {
270 const mode = ionicGlobal.getIonMode(this);
271 const interfaceOptions = this.interfaceOptions;
272 const actionSheetOpts = Object.assign(Object.assign({ mode }, interfaceOptions), { buttons: this.createActionSheetButtons(this.childOpts, this.value), cssClass: ['select-action-sheet', interfaceOptions.cssClass] });
273 return overlays.actionSheetController.create(actionSheetOpts);
274 }
275 async openAlert() {
276 const label = this.getLabel();
277 const labelText = (label) ? label.textContent : null;
278 const interfaceOptions = this.interfaceOptions;
279 const inputType = (this.multiple ? 'checkbox' : 'radio');
280 const mode = ionicGlobal.getIonMode(this);
281 const alertOpts = Object.assign(Object.assign({ mode }, interfaceOptions), { header: interfaceOptions.header ? interfaceOptions.header : labelText, inputs: this.createAlertInputs(this.childOpts, inputType, this.value), buttons: [
282 {
283 text: this.cancelText,
284 role: 'cancel',
285 handler: () => {
286 this.ionCancel.emit();
287 }
288 },
289 {
290 text: this.okText,
291 handler: (selectedValues) => {
292 this.value = selectedValues;
293 }
294 }
295 ], cssClass: ['select-alert', interfaceOptions.cssClass,
296 (this.multiple ? 'multiple-select-alert' : 'single-select-alert')] });
297 return overlays.alertController.create(alertOpts);
298 }
299 /**
300 * Close the select interface.
301 */
302 close() {
303 // TODO check !this.overlay || !this.isFocus()
304 if (!this.overlay) {
305 return Promise.resolve(false);
306 }
307 return this.overlay.dismiss();
308 }
309 getLabel() {
310 return helpers.findItemLabel(this.el);
311 }
312 hasValue() {
313 return this.getText() !== '';
314 }
315 get childOpts() {
316 return Array.from(this.el.querySelectorAll('ion-select-option'));
317 }
318 getText() {
319 const selectedText = this.selectedText;
320 if (selectedText != null && selectedText !== '') {
321 return selectedText;
322 }
323 return generateText(this.childOpts, this.value, this.compareWith);
324 }
325 setFocus() {
326 if (this.focusEl) {
327 this.focusEl.focus();
328 }
329 }
330 emitStyle() {
331 this.ionStyle.emit({
332 'interactive': true,
333 'select': true,
334 'has-placeholder': this.placeholder != null,
335 'has-value': this.hasValue(),
336 'interactive-disabled': this.disabled,
337 'select-disabled': this.disabled
338 });
339 }
340 render() {
341 const { disabled, el, inputId, isExpanded, name, placeholder, value } = this;
342 const mode = ionicGlobal.getIonMode(this);
343 const { labelText, labelId } = helpers.getAriaLabel(el, inputId);
344 helpers.renderHiddenInput(true, el, name, parseValue(value), disabled);
345 const displayValue = this.getText();
346 let addPlaceholderClass = false;
347 let selectText = displayValue;
348 if (selectText === '' && placeholder != null) {
349 selectText = placeholder;
350 addPlaceholderClass = true;
351 }
352 const selectTextClasses = {
353 'select-text': true,
354 'select-placeholder': addPlaceholderClass
355 };
356 const textPart = addPlaceholderClass ? 'placeholder' : 'text';
357 // If there is a label then we need to concatenate it with the
358 // current value (or placeholder) and a comma so it separates
359 // nicely when the screen reader announces it, otherwise just
360 // announce the value / placeholder
361 const displayLabel = labelText !== undefined
362 ? (selectText !== '' ? `${selectText}, ${labelText}` : labelText)
363 : selectText;
364 return (index.h(index.Host, { onClick: this.onClick, role: "button", "aria-haspopup": "listbox", "aria-disabled": disabled ? 'true' : null, "aria-label": displayLabel, class: {
365 [mode]: true,
366 'in-item': theme.hostContext('ion-item', el),
367 'select-disabled': disabled,
368 'select-expanded': isExpanded
369 } }, index.h("div", { "aria-hidden": "true", class: selectTextClasses, part: textPart }, selectText), index.h("div", { class: "select-icon", role: "presentation", part: "icon" }, index.h("div", { class: "select-icon-inner" })), index.h("label", { id: labelId }, displayLabel), index.h("button", { type: "button", disabled: disabled, id: inputId, "aria-labelledby": labelId, "aria-haspopup": "listbox", "aria-expanded": `${isExpanded}`, onFocus: this.onFocus, onBlur: this.onBlur, ref: (focusEl => this.focusEl = focusEl) })));
370 }
371 get el() { return index.getElement(this); }
372 static get watchers() { return {
373 "disabled": ["disabledChanged"],
374 "placeholder": ["disabledChanged"],
375 "value": ["valueChanged"]
376 }; }
377};
378const isOptionSelected = (currentValue, compareValue, compareWith) => {
379 if (currentValue === undefined) {
380 return false;
381 }
382 if (Array.isArray(currentValue)) {
383 return currentValue.some(val => compareOptions(val, compareValue, compareWith));
384 }
385 else {
386 return compareOptions(currentValue, compareValue, compareWith);
387 }
388};
389const getOptionValue = (el) => {
390 const value = el.value;
391 return (value === undefined)
392 ? el.textContent || ''
393 : value;
394};
395const parseValue = (value) => {
396 if (value == null) {
397 return undefined;
398 }
399 if (Array.isArray(value)) {
400 return value.join(',');
401 }
402 return value.toString();
403};
404const compareOptions = (currentValue, compareValue, compareWith) => {
405 if (typeof compareWith === 'function') {
406 return compareWith(currentValue, compareValue);
407 }
408 else if (typeof compareWith === 'string') {
409 return currentValue[compareWith] === compareValue[compareWith];
410 }
411 else {
412 return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
413 }
414};
415const generateText = (opts, value, compareWith) => {
416 if (value === undefined) {
417 return '';
418 }
419 if (Array.isArray(value)) {
420 return value
421 .map(v => textForValue(opts, v, compareWith))
422 .filter(opt => opt !== null)
423 .join(', ');
424 }
425 else {
426 return textForValue(opts, value, compareWith) || '';
427 }
428};
429const textForValue = (opts, value, compareWith) => {
430 const selectOpt = opts.find(opt => {
431 return compareOptions(getOptionValue(opt), value, compareWith);
432 });
433 return selectOpt
434 ? selectOpt.textContent
435 : null;
436};
437let selectIds = 0;
438const OPTION_CLASS = 'select-interface-option';
439Select.style = {
440 ios: selectIosCss,
441 md: selectMdCss
442};
443
444const selectOptionCss = ":host{display:none}";
445
446const SelectOption = class {
447 constructor(hostRef) {
448 index.registerInstance(this, hostRef);
449 this.inputId = `ion-selopt-${selectOptionIds++}`;
450 /**
451 * If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons.
452 */
453 this.disabled = false;
454 }
455 render() {
456 return (index.h(index.Host, { role: "option", id: this.inputId, class: ionicGlobal.getIonMode(this) }));
457 }
458 get el() { return index.getElement(this); }
459};
460let selectOptionIds = 0;
461SelectOption.style = selectOptionCss;
462
463const selectPopoverCss = ".sc-ion-select-popover-h ion-list.sc-ion-select-popover{margin-left:0;margin-right:0;margin-top:-1px;margin-bottom:-1px}.sc-ion-select-popover-h ion-list-header.sc-ion-select-popover,.sc-ion-select-popover-h ion-label.sc-ion-select-popover{margin-left:0;margin-right:0;margin-top:0;margin-bottom:0}";
464
465const SelectPopover = class {
466 constructor(hostRef) {
467 index.registerInstance(this, hostRef);
468 /** Array of options for the popover */
469 this.options = [];
470 }
471 onSelect(ev) {
472 const option = this.options.find(o => o.value === ev.target.value);
473 if (option) {
474 overlays.safeCall(option.handler);
475 }
476 }
477 render() {
478 const checkedOption = this.options.find(o => o.checked);
479 const checkedValue = checkedOption ? checkedOption.value : undefined;
480 return (index.h(index.Host, { class: ionicGlobal.getIonMode(this) }, index.h("ion-list", null, this.header !== undefined && index.h("ion-list-header", null, this.header), (this.subHeader !== undefined || this.message !== undefined) &&
481 index.h("ion-item", null, index.h("ion-label", { class: "ion-text-wrap" }, this.subHeader !== undefined && index.h("h3", null, this.subHeader), this.message !== undefined && index.h("p", null, this.message))), index.h("ion-radio-group", { value: checkedValue }, this.options.map(option => index.h("ion-item", { class: theme.getClassMap(option.cssClass) }, index.h("ion-label", null, option.text), index.h("ion-radio", { value: option.value, disabled: option.disabled })))))));
482 }
483};
484SelectPopover.style = selectPopoverCss;
485
486exports.ion_select = Select;
487exports.ion_select_option = SelectOption;
488exports.ion_select_popover = SelectPopover;