@ionic/core 7.7.4 → 7.7.5-nightly.20240307
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/components/checkbox.js +1 -1
- package/components/ion-range.js +126 -21
- package/dist/cjs/ion-checkbox.cjs.entry.js +1 -1
- package/dist/cjs/ion-range.cjs.entry.js +126 -21
- package/dist/collection/components/checkbox/checkbox.js +1 -1
- package/dist/collection/components/checkbox/test/checkbox.spec.js +12 -0
- package/dist/collection/components/range/range.js +126 -21
- package/dist/collection/components/range/test/range-events.e2e.js +27 -0
- package/dist/docs.json +1 -1
- package/dist/esm/ion-checkbox.entry.js +1 -1
- package/dist/esm/ion-range.entry.js +126 -21
- package/dist/esm-es5/ion-checkbox.entry.js +1 -1
- package/dist/esm-es5/ion-range.entry.js +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-134d4936.system.js +1 -1
- package/dist/ionic/{p-8a46dbfe.system.entry.js → p-4573d495.system.entry.js} +1 -1
- package/dist/ionic/p-c79b0afa.entry.js +4 -0
- package/dist/ionic/{p-e23090ae.system.entry.js → p-c85693ad.system.entry.js} +1 -1
- package/dist/ionic/p-f553e0c0.entry.js +4 -0
- package/dist/types/components/range/range.d.ts +29 -0
- package/hydrate/index.js +127 -22
- package/package.json +1 -1
- package/dist/ionic/p-38a75a00.entry.js +0 -4
- package/dist/ionic/p-c977af5e.entry.js +0 -4
package/components/checkbox.js
CHANGED
|
@@ -110,7 +110,7 @@ const Checkbox = /*@__PURE__*/ proxyCustomElement(class Checkbox extends HTMLEle
|
|
|
110
110
|
const mode = getIonMode(this);
|
|
111
111
|
const path = getSVGPath(mode, indeterminate);
|
|
112
112
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
113
|
-
return (h(Host, { class: createColorClasses(color, {
|
|
113
|
+
return (h(Host, { "aria-checked": indeterminate ? 'mixed' : `${checked}`, class: createColorClasses(color, {
|
|
114
114
|
[mode]: true,
|
|
115
115
|
'in-item': hostContext('ion-item', el),
|
|
116
116
|
'checkbox-checked': checked,
|
package/components/ion-range.js
CHANGED
|
@@ -100,8 +100,14 @@ const Range = /*@__PURE__*/ proxyCustomElement(class Range extends HTMLElement {
|
|
|
100
100
|
el: rangeSlider,
|
|
101
101
|
gestureName: 'range',
|
|
102
102
|
gesturePriority: 100,
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Provide a threshold since the drag movement
|
|
105
|
+
* might be a user scrolling the view.
|
|
106
|
+
* If this is true, then the range
|
|
107
|
+
* should not move.
|
|
108
|
+
*/
|
|
109
|
+
threshold: 10,
|
|
110
|
+
onStart: () => this.onStart(),
|
|
105
111
|
onMove: (ev) => this.onMove(ev),
|
|
106
112
|
onEnd: (ev) => this.onEnd(ev),
|
|
107
113
|
});
|
|
@@ -284,33 +290,93 @@ const Range = /*@__PURE__*/ proxyCustomElement(class Range extends HTMLElement {
|
|
|
284
290
|
this.value = this.ensureValueInBounds(this.value);
|
|
285
291
|
this.ionChange.emit({ value: this.value });
|
|
286
292
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
293
|
+
/**
|
|
294
|
+
* The value should be updated on touch end or
|
|
295
|
+
* when the component is being dragged.
|
|
296
|
+
* This follows the native behavior of mobile devices.
|
|
297
|
+
*
|
|
298
|
+
* For example: When the user lifts their finger from the
|
|
299
|
+
* screen after tapping the bar or dragging the bar or knob.
|
|
300
|
+
*/
|
|
301
|
+
onStart() {
|
|
302
|
+
this.ionKnobMoveStart.emit({ value: this.ensureValueInBounds(this.value) });
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The value should be updated while dragging the
|
|
306
|
+
* bar or knob.
|
|
307
|
+
*
|
|
308
|
+
* While the user is dragging, the view
|
|
309
|
+
* should not scroll. This is to prevent the user from
|
|
310
|
+
* feeling disoriented while dragging.
|
|
311
|
+
*
|
|
312
|
+
* The user can scroll on the view if the knob or
|
|
313
|
+
* bar is not being dragged.
|
|
314
|
+
*
|
|
315
|
+
* @param detail The details of the gesture event.
|
|
316
|
+
*/
|
|
317
|
+
onMove(detail) {
|
|
318
|
+
const { contentEl, pressedKnob } = this;
|
|
319
|
+
const currentX = detail.currentX;
|
|
320
|
+
/**
|
|
321
|
+
* Since the user is dragging on the bar or knob, the view should not scroll.
|
|
322
|
+
*
|
|
323
|
+
* This only needs to be done once.
|
|
324
|
+
*/
|
|
325
|
+
if (contentEl && this.initialContentScrollY === undefined) {
|
|
290
326
|
this.initialContentScrollY = disableContentScrollY(contentEl);
|
|
291
327
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
328
|
+
/**
|
|
329
|
+
* The `pressedKnob` can be undefined if the user just
|
|
330
|
+
* started dragging the knob.
|
|
331
|
+
*
|
|
332
|
+
* This is necessary to determine which knob the user is dragging,
|
|
333
|
+
* especially when it's a dual knob.
|
|
334
|
+
* Plus, it determines when to apply certain styles.
|
|
335
|
+
*
|
|
336
|
+
* This only needs to be done once since the knob won't change
|
|
337
|
+
* while the user is dragging.
|
|
338
|
+
*/
|
|
339
|
+
if (pressedKnob === undefined) {
|
|
340
|
+
this.setPressedKnob(currentX);
|
|
298
341
|
}
|
|
299
|
-
this.pressedKnob = !this.dualKnobs || Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio) ? 'A' : 'B';
|
|
300
|
-
this.setFocus(this.pressedKnob);
|
|
301
|
-
// update the active knob's position
|
|
302
342
|
this.update(currentX);
|
|
303
|
-
this.ionKnobMoveStart.emit({ value: this.ensureValueInBounds(this.value) });
|
|
304
|
-
}
|
|
305
|
-
onMove(detail) {
|
|
306
|
-
this.update(detail.currentX);
|
|
307
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* The value should be updated on touch end:
|
|
346
|
+
* - When the user lifts their finger from the screen after
|
|
347
|
+
* tapping the bar.
|
|
348
|
+
*
|
|
349
|
+
* @param detail The details of the gesture or mouse event.
|
|
350
|
+
*/
|
|
308
351
|
onEnd(detail) {
|
|
309
352
|
const { contentEl, initialContentScrollY } = this;
|
|
310
|
-
|
|
353
|
+
const currentX = detail.currentX || detail.clientX;
|
|
354
|
+
/**
|
|
355
|
+
* The `pressedKnob` can be undefined if the user never
|
|
356
|
+
* dragged the knob. They just tapped on the bar.
|
|
357
|
+
*
|
|
358
|
+
* This is necessary to determine which knob the user is changing,
|
|
359
|
+
* especially when it's a dual knob.
|
|
360
|
+
* Plus, it determines when to apply certain styles.
|
|
361
|
+
*/
|
|
362
|
+
if (this.pressedKnob === undefined) {
|
|
363
|
+
this.setPressedKnob(currentX);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* The user is no longer dragging the bar or
|
|
367
|
+
* knob (if they were dragging it).
|
|
368
|
+
*
|
|
369
|
+
* The user can now scroll on the view in the next gesture event.
|
|
370
|
+
*/
|
|
371
|
+
if (contentEl && initialContentScrollY !== undefined) {
|
|
311
372
|
resetContentScrollY(contentEl, initialContentScrollY);
|
|
312
373
|
}
|
|
313
|
-
|
|
374
|
+
// update the active knob's position
|
|
375
|
+
this.update(currentX);
|
|
376
|
+
/**
|
|
377
|
+
* Reset the pressed knob to undefined since the user
|
|
378
|
+
* may start dragging a different knob in the next gesture event.
|
|
379
|
+
*/
|
|
314
380
|
this.pressedKnob = undefined;
|
|
315
381
|
this.emitValueChange();
|
|
316
382
|
this.ionKnobMoveEnd.emit({ value: this.ensureValueInBounds(this.value) });
|
|
@@ -337,6 +403,16 @@ const Range = /*@__PURE__*/ proxyCustomElement(class Range extends HTMLElement {
|
|
|
337
403
|
// Update input value
|
|
338
404
|
this.updateValue();
|
|
339
405
|
}
|
|
406
|
+
setPressedKnob(currentX) {
|
|
407
|
+
const rect = (this.rect = this.rangeSlider.getBoundingClientRect());
|
|
408
|
+
// figure out which knob they started closer to
|
|
409
|
+
let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
|
|
410
|
+
if (isRTL(this.el)) {
|
|
411
|
+
ratio = 1 - ratio;
|
|
412
|
+
}
|
|
413
|
+
this.pressedKnob = !this.dualKnobs || Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio) ? 'A' : 'B';
|
|
414
|
+
this.setFocus(this.pressedKnob);
|
|
415
|
+
}
|
|
340
416
|
get valA() {
|
|
341
417
|
return ratioToValue(this.ratioA, this.min, this.max, this.step);
|
|
342
418
|
}
|
|
@@ -539,7 +615,36 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
|
|
539
615
|
if (!this.legacyFormController.hasLegacyControl() && this.hasLabel) {
|
|
540
616
|
labelledBy = 'range-label';
|
|
541
617
|
}
|
|
542
|
-
return (h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl)
|
|
618
|
+
return (h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl),
|
|
619
|
+
/**
|
|
620
|
+
* Since the gesture has a threshold, the value
|
|
621
|
+
* won't change until the user has dragged past
|
|
622
|
+
* the threshold. This is to prevent the range
|
|
623
|
+
* from moving when the user is scrolling.
|
|
624
|
+
*
|
|
625
|
+
* This results in the value not being updated
|
|
626
|
+
* and the event emitters not being triggered
|
|
627
|
+
* if the user taps on the range. This is why
|
|
628
|
+
* we need to listen for the "pointerUp" event.
|
|
629
|
+
*/
|
|
630
|
+
onPointerUp: (ev) => {
|
|
631
|
+
/**
|
|
632
|
+
* If the user drags the knob on the web
|
|
633
|
+
* version (does not occur on mobile),
|
|
634
|
+
* the "pointerUp" event will be triggered
|
|
635
|
+
* along with the gesture's events.
|
|
636
|
+
* This leads to duplicate events.
|
|
637
|
+
*
|
|
638
|
+
* By checking if the pressedKnob is undefined,
|
|
639
|
+
* we can determine if the "pointerUp" event was
|
|
640
|
+
* triggered by a tap or a drag. If it was
|
|
641
|
+
* dragged, the pressedKnob will be defined.
|
|
642
|
+
*/
|
|
643
|
+
if (this.pressedKnob === undefined) {
|
|
644
|
+
this.onStart();
|
|
645
|
+
this.onEnd(ev);
|
|
646
|
+
}
|
|
647
|
+
} }, ticks.map((tick) => (h("div", { style: tickStyle(tick), role: "presentation", class: {
|
|
543
648
|
'range-tick': true,
|
|
544
649
|
'range-tick-active': tick.active,
|
|
545
650
|
}, part: tick.active ? 'tick-active' : 'tick' }))), h("div", { class: "range-bar-container" }, h("div", { class: "range-bar", role: "presentation", part: "bar" }), h("div", { class: {
|
|
@@ -112,7 +112,7 @@ const Checkbox = class {
|
|
|
112
112
|
const mode = ionicGlobal.getIonMode(this);
|
|
113
113
|
const path = getSVGPath(mode, indeterminate);
|
|
114
114
|
helpers.renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
115
|
-
return (index.h(index.Host, { class: theme.createColorClasses(color, {
|
|
115
|
+
return (index.h(index.Host, { "aria-checked": indeterminate ? 'mixed' : `${checked}`, class: theme.createColorClasses(color, {
|
|
116
116
|
[mode]: true,
|
|
117
117
|
'in-item': theme.hostContext('ion-item', el),
|
|
118
118
|
'checkbox-checked': checked,
|
|
@@ -102,8 +102,14 @@ const Range = class {
|
|
|
102
102
|
el: rangeSlider,
|
|
103
103
|
gestureName: 'range',
|
|
104
104
|
gesturePriority: 100,
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Provide a threshold since the drag movement
|
|
107
|
+
* might be a user scrolling the view.
|
|
108
|
+
* If this is true, then the range
|
|
109
|
+
* should not move.
|
|
110
|
+
*/
|
|
111
|
+
threshold: 10,
|
|
112
|
+
onStart: () => this.onStart(),
|
|
107
113
|
onMove: (ev) => this.onMove(ev),
|
|
108
114
|
onEnd: (ev) => this.onEnd(ev),
|
|
109
115
|
});
|
|
@@ -286,33 +292,93 @@ const Range = class {
|
|
|
286
292
|
this.value = this.ensureValueInBounds(this.value);
|
|
287
293
|
this.ionChange.emit({ value: this.value });
|
|
288
294
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
295
|
+
/**
|
|
296
|
+
* The value should be updated on touch end or
|
|
297
|
+
* when the component is being dragged.
|
|
298
|
+
* This follows the native behavior of mobile devices.
|
|
299
|
+
*
|
|
300
|
+
* For example: When the user lifts their finger from the
|
|
301
|
+
* screen after tapping the bar or dragging the bar or knob.
|
|
302
|
+
*/
|
|
303
|
+
onStart() {
|
|
304
|
+
this.ionKnobMoveStart.emit({ value: this.ensureValueInBounds(this.value) });
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The value should be updated while dragging the
|
|
308
|
+
* bar or knob.
|
|
309
|
+
*
|
|
310
|
+
* While the user is dragging, the view
|
|
311
|
+
* should not scroll. This is to prevent the user from
|
|
312
|
+
* feeling disoriented while dragging.
|
|
313
|
+
*
|
|
314
|
+
* The user can scroll on the view if the knob or
|
|
315
|
+
* bar is not being dragged.
|
|
316
|
+
*
|
|
317
|
+
* @param detail The details of the gesture event.
|
|
318
|
+
*/
|
|
319
|
+
onMove(detail) {
|
|
320
|
+
const { contentEl, pressedKnob } = this;
|
|
321
|
+
const currentX = detail.currentX;
|
|
322
|
+
/**
|
|
323
|
+
* Since the user is dragging on the bar or knob, the view should not scroll.
|
|
324
|
+
*
|
|
325
|
+
* This only needs to be done once.
|
|
326
|
+
*/
|
|
327
|
+
if (contentEl && this.initialContentScrollY === undefined) {
|
|
292
328
|
this.initialContentScrollY = index$2.disableContentScrollY(contentEl);
|
|
293
329
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
330
|
+
/**
|
|
331
|
+
* The `pressedKnob` can be undefined if the user just
|
|
332
|
+
* started dragging the knob.
|
|
333
|
+
*
|
|
334
|
+
* This is necessary to determine which knob the user is dragging,
|
|
335
|
+
* especially when it's a dual knob.
|
|
336
|
+
* Plus, it determines when to apply certain styles.
|
|
337
|
+
*
|
|
338
|
+
* This only needs to be done once since the knob won't change
|
|
339
|
+
* while the user is dragging.
|
|
340
|
+
*/
|
|
341
|
+
if (pressedKnob === undefined) {
|
|
342
|
+
this.setPressedKnob(currentX);
|
|
300
343
|
}
|
|
301
|
-
this.pressedKnob = !this.dualKnobs || Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio) ? 'A' : 'B';
|
|
302
|
-
this.setFocus(this.pressedKnob);
|
|
303
|
-
// update the active knob's position
|
|
304
344
|
this.update(currentX);
|
|
305
|
-
this.ionKnobMoveStart.emit({ value: this.ensureValueInBounds(this.value) });
|
|
306
|
-
}
|
|
307
|
-
onMove(detail) {
|
|
308
|
-
this.update(detail.currentX);
|
|
309
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* The value should be updated on touch end:
|
|
348
|
+
* - When the user lifts their finger from the screen after
|
|
349
|
+
* tapping the bar.
|
|
350
|
+
*
|
|
351
|
+
* @param detail The details of the gesture or mouse event.
|
|
352
|
+
*/
|
|
310
353
|
onEnd(detail) {
|
|
311
354
|
const { contentEl, initialContentScrollY } = this;
|
|
312
|
-
|
|
355
|
+
const currentX = detail.currentX || detail.clientX;
|
|
356
|
+
/**
|
|
357
|
+
* The `pressedKnob` can be undefined if the user never
|
|
358
|
+
* dragged the knob. They just tapped on the bar.
|
|
359
|
+
*
|
|
360
|
+
* This is necessary to determine which knob the user is changing,
|
|
361
|
+
* especially when it's a dual knob.
|
|
362
|
+
* Plus, it determines when to apply certain styles.
|
|
363
|
+
*/
|
|
364
|
+
if (this.pressedKnob === undefined) {
|
|
365
|
+
this.setPressedKnob(currentX);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The user is no longer dragging the bar or
|
|
369
|
+
* knob (if they were dragging it).
|
|
370
|
+
*
|
|
371
|
+
* The user can now scroll on the view in the next gesture event.
|
|
372
|
+
*/
|
|
373
|
+
if (contentEl && initialContentScrollY !== undefined) {
|
|
313
374
|
index$2.resetContentScrollY(contentEl, initialContentScrollY);
|
|
314
375
|
}
|
|
315
|
-
|
|
376
|
+
// update the active knob's position
|
|
377
|
+
this.update(currentX);
|
|
378
|
+
/**
|
|
379
|
+
* Reset the pressed knob to undefined since the user
|
|
380
|
+
* may start dragging a different knob in the next gesture event.
|
|
381
|
+
*/
|
|
316
382
|
this.pressedKnob = undefined;
|
|
317
383
|
this.emitValueChange();
|
|
318
384
|
this.ionKnobMoveEnd.emit({ value: this.ensureValueInBounds(this.value) });
|
|
@@ -339,6 +405,16 @@ const Range = class {
|
|
|
339
405
|
// Update input value
|
|
340
406
|
this.updateValue();
|
|
341
407
|
}
|
|
408
|
+
setPressedKnob(currentX) {
|
|
409
|
+
const rect = (this.rect = this.rangeSlider.getBoundingClientRect());
|
|
410
|
+
// figure out which knob they started closer to
|
|
411
|
+
let ratio = helpers.clamp(0, (currentX - rect.left) / rect.width, 1);
|
|
412
|
+
if (dir.isRTL(this.el)) {
|
|
413
|
+
ratio = 1 - ratio;
|
|
414
|
+
}
|
|
415
|
+
this.pressedKnob = !this.dualKnobs || Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio) ? 'A' : 'B';
|
|
416
|
+
this.setFocus(this.pressedKnob);
|
|
417
|
+
}
|
|
342
418
|
get valA() {
|
|
343
419
|
return ratioToValue(this.ratioA, this.min, this.max, this.step);
|
|
344
420
|
}
|
|
@@ -541,7 +617,36 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
|
|
541
617
|
if (!this.legacyFormController.hasLegacyControl() && this.hasLabel) {
|
|
542
618
|
labelledBy = 'range-label';
|
|
543
619
|
}
|
|
544
|
-
return (index.h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl)
|
|
620
|
+
return (index.h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl),
|
|
621
|
+
/**
|
|
622
|
+
* Since the gesture has a threshold, the value
|
|
623
|
+
* won't change until the user has dragged past
|
|
624
|
+
* the threshold. This is to prevent the range
|
|
625
|
+
* from moving when the user is scrolling.
|
|
626
|
+
*
|
|
627
|
+
* This results in the value not being updated
|
|
628
|
+
* and the event emitters not being triggered
|
|
629
|
+
* if the user taps on the range. This is why
|
|
630
|
+
* we need to listen for the "pointerUp" event.
|
|
631
|
+
*/
|
|
632
|
+
onPointerUp: (ev) => {
|
|
633
|
+
/**
|
|
634
|
+
* If the user drags the knob on the web
|
|
635
|
+
* version (does not occur on mobile),
|
|
636
|
+
* the "pointerUp" event will be triggered
|
|
637
|
+
* along with the gesture's events.
|
|
638
|
+
* This leads to duplicate events.
|
|
639
|
+
*
|
|
640
|
+
* By checking if the pressedKnob is undefined,
|
|
641
|
+
* we can determine if the "pointerUp" event was
|
|
642
|
+
* triggered by a tap or a drag. If it was
|
|
643
|
+
* dragged, the pressedKnob will be defined.
|
|
644
|
+
*/
|
|
645
|
+
if (this.pressedKnob === undefined) {
|
|
646
|
+
this.onStart();
|
|
647
|
+
this.onEnd(ev);
|
|
648
|
+
}
|
|
649
|
+
} }, ticks.map((tick) => (index.h("div", { style: tickStyle(tick), role: "presentation", class: {
|
|
545
650
|
'range-tick': true,
|
|
546
651
|
'range-tick-active': tick.active,
|
|
547
652
|
}, part: tick.active ? 'tick-active' : 'tick' }))), index.h("div", { class: "range-bar-container" }, index.h("div", { class: "range-bar", role: "presentation", part: "bar" }), index.h("div", { class: {
|
|
@@ -105,7 +105,7 @@ export class Checkbox {
|
|
|
105
105
|
const mode = getIonMode(this);
|
|
106
106
|
const path = getSVGPath(mode, indeterminate);
|
|
107
107
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
108
|
-
return (h(Host, { class: createColorClasses(color, {
|
|
108
|
+
return (h(Host, { "aria-checked": indeterminate ? 'mixed' : `${checked}`, class: createColorClasses(color, {
|
|
109
109
|
[mode]: true,
|
|
110
110
|
'in-item': hostContext('ion-item', el),
|
|
111
111
|
'checkbox-checked': checked,
|
|
@@ -32,3 +32,15 @@ describe('ion-checkbox: disabled', () => {
|
|
|
32
32
|
expect(checkbox.checked).toBe(false);
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
|
+
describe('ion-checkbox: indeterminate', () => {
|
|
36
|
+
it('should have a mixed value for aria-checked', async () => {
|
|
37
|
+
const page = await newSpecPage({
|
|
38
|
+
components: [Checkbox],
|
|
39
|
+
html: `
|
|
40
|
+
<ion-checkbox indeterminate="true">Checkbox</ion-checkbox>
|
|
41
|
+
`,
|
|
42
|
+
});
|
|
43
|
+
const checkbox = page.body.querySelector('ion-checkbox');
|
|
44
|
+
expect(checkbox.getAttribute('aria-checked')).toBe('mixed');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -58,8 +58,14 @@ export class Range {
|
|
|
58
58
|
el: rangeSlider,
|
|
59
59
|
gestureName: 'range',
|
|
60
60
|
gesturePriority: 100,
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Provide a threshold since the drag movement
|
|
63
|
+
* might be a user scrolling the view.
|
|
64
|
+
* If this is true, then the range
|
|
65
|
+
* should not move.
|
|
66
|
+
*/
|
|
67
|
+
threshold: 10,
|
|
68
|
+
onStart: () => this.onStart(),
|
|
63
69
|
onMove: (ev) => this.onMove(ev),
|
|
64
70
|
onEnd: (ev) => this.onEnd(ev),
|
|
65
71
|
});
|
|
@@ -242,33 +248,93 @@ export class Range {
|
|
|
242
248
|
this.value = this.ensureValueInBounds(this.value);
|
|
243
249
|
this.ionChange.emit({ value: this.value });
|
|
244
250
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
251
|
+
/**
|
|
252
|
+
* The value should be updated on touch end or
|
|
253
|
+
* when the component is being dragged.
|
|
254
|
+
* This follows the native behavior of mobile devices.
|
|
255
|
+
*
|
|
256
|
+
* For example: When the user lifts their finger from the
|
|
257
|
+
* screen after tapping the bar or dragging the bar or knob.
|
|
258
|
+
*/
|
|
259
|
+
onStart() {
|
|
260
|
+
this.ionKnobMoveStart.emit({ value: this.ensureValueInBounds(this.value) });
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The value should be updated while dragging the
|
|
264
|
+
* bar or knob.
|
|
265
|
+
*
|
|
266
|
+
* While the user is dragging, the view
|
|
267
|
+
* should not scroll. This is to prevent the user from
|
|
268
|
+
* feeling disoriented while dragging.
|
|
269
|
+
*
|
|
270
|
+
* The user can scroll on the view if the knob or
|
|
271
|
+
* bar is not being dragged.
|
|
272
|
+
*
|
|
273
|
+
* @param detail The details of the gesture event.
|
|
274
|
+
*/
|
|
275
|
+
onMove(detail) {
|
|
276
|
+
const { contentEl, pressedKnob } = this;
|
|
277
|
+
const currentX = detail.currentX;
|
|
278
|
+
/**
|
|
279
|
+
* Since the user is dragging on the bar or knob, the view should not scroll.
|
|
280
|
+
*
|
|
281
|
+
* This only needs to be done once.
|
|
282
|
+
*/
|
|
283
|
+
if (contentEl && this.initialContentScrollY === undefined) {
|
|
248
284
|
this.initialContentScrollY = disableContentScrollY(contentEl);
|
|
249
285
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
286
|
+
/**
|
|
287
|
+
* The `pressedKnob` can be undefined if the user just
|
|
288
|
+
* started dragging the knob.
|
|
289
|
+
*
|
|
290
|
+
* This is necessary to determine which knob the user is dragging,
|
|
291
|
+
* especially when it's a dual knob.
|
|
292
|
+
* Plus, it determines when to apply certain styles.
|
|
293
|
+
*
|
|
294
|
+
* This only needs to be done once since the knob won't change
|
|
295
|
+
* while the user is dragging.
|
|
296
|
+
*/
|
|
297
|
+
if (pressedKnob === undefined) {
|
|
298
|
+
this.setPressedKnob(currentX);
|
|
256
299
|
}
|
|
257
|
-
this.pressedKnob = !this.dualKnobs || Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio) ? 'A' : 'B';
|
|
258
|
-
this.setFocus(this.pressedKnob);
|
|
259
|
-
// update the active knob's position
|
|
260
300
|
this.update(currentX);
|
|
261
|
-
this.ionKnobMoveStart.emit({ value: this.ensureValueInBounds(this.value) });
|
|
262
|
-
}
|
|
263
|
-
onMove(detail) {
|
|
264
|
-
this.update(detail.currentX);
|
|
265
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* The value should be updated on touch end:
|
|
304
|
+
* - When the user lifts their finger from the screen after
|
|
305
|
+
* tapping the bar.
|
|
306
|
+
*
|
|
307
|
+
* @param detail The details of the gesture or mouse event.
|
|
308
|
+
*/
|
|
266
309
|
onEnd(detail) {
|
|
267
310
|
const { contentEl, initialContentScrollY } = this;
|
|
268
|
-
|
|
311
|
+
const currentX = detail.currentX || detail.clientX;
|
|
312
|
+
/**
|
|
313
|
+
* The `pressedKnob` can be undefined if the user never
|
|
314
|
+
* dragged the knob. They just tapped on the bar.
|
|
315
|
+
*
|
|
316
|
+
* This is necessary to determine which knob the user is changing,
|
|
317
|
+
* especially when it's a dual knob.
|
|
318
|
+
* Plus, it determines when to apply certain styles.
|
|
319
|
+
*/
|
|
320
|
+
if (this.pressedKnob === undefined) {
|
|
321
|
+
this.setPressedKnob(currentX);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* The user is no longer dragging the bar or
|
|
325
|
+
* knob (if they were dragging it).
|
|
326
|
+
*
|
|
327
|
+
* The user can now scroll on the view in the next gesture event.
|
|
328
|
+
*/
|
|
329
|
+
if (contentEl && initialContentScrollY !== undefined) {
|
|
269
330
|
resetContentScrollY(contentEl, initialContentScrollY);
|
|
270
331
|
}
|
|
271
|
-
|
|
332
|
+
// update the active knob's position
|
|
333
|
+
this.update(currentX);
|
|
334
|
+
/**
|
|
335
|
+
* Reset the pressed knob to undefined since the user
|
|
336
|
+
* may start dragging a different knob in the next gesture event.
|
|
337
|
+
*/
|
|
272
338
|
this.pressedKnob = undefined;
|
|
273
339
|
this.emitValueChange();
|
|
274
340
|
this.ionKnobMoveEnd.emit({ value: this.ensureValueInBounds(this.value) });
|
|
@@ -295,6 +361,16 @@ export class Range {
|
|
|
295
361
|
// Update input value
|
|
296
362
|
this.updateValue();
|
|
297
363
|
}
|
|
364
|
+
setPressedKnob(currentX) {
|
|
365
|
+
const rect = (this.rect = this.rangeSlider.getBoundingClientRect());
|
|
366
|
+
// figure out which knob they started closer to
|
|
367
|
+
let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
|
|
368
|
+
if (isRTL(this.el)) {
|
|
369
|
+
ratio = 1 - ratio;
|
|
370
|
+
}
|
|
371
|
+
this.pressedKnob = !this.dualKnobs || Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio) ? 'A' : 'B';
|
|
372
|
+
this.setFocus(this.pressedKnob);
|
|
373
|
+
}
|
|
298
374
|
get valA() {
|
|
299
375
|
return ratioToValue(this.ratioA, this.min, this.max, this.step);
|
|
300
376
|
}
|
|
@@ -497,7 +573,36 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
|
|
497
573
|
if (!this.legacyFormController.hasLegacyControl() && this.hasLabel) {
|
|
498
574
|
labelledBy = 'range-label';
|
|
499
575
|
}
|
|
500
|
-
return (h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl)
|
|
576
|
+
return (h("div", { class: "range-slider", ref: (rangeEl) => (this.rangeSlider = rangeEl),
|
|
577
|
+
/**
|
|
578
|
+
* Since the gesture has a threshold, the value
|
|
579
|
+
* won't change until the user has dragged past
|
|
580
|
+
* the threshold. This is to prevent the range
|
|
581
|
+
* from moving when the user is scrolling.
|
|
582
|
+
*
|
|
583
|
+
* This results in the value not being updated
|
|
584
|
+
* and the event emitters not being triggered
|
|
585
|
+
* if the user taps on the range. This is why
|
|
586
|
+
* we need to listen for the "pointerUp" event.
|
|
587
|
+
*/
|
|
588
|
+
onPointerUp: (ev) => {
|
|
589
|
+
/**
|
|
590
|
+
* If the user drags the knob on the web
|
|
591
|
+
* version (does not occur on mobile),
|
|
592
|
+
* the "pointerUp" event will be triggered
|
|
593
|
+
* along with the gesture's events.
|
|
594
|
+
* This leads to duplicate events.
|
|
595
|
+
*
|
|
596
|
+
* By checking if the pressedKnob is undefined,
|
|
597
|
+
* we can determine if the "pointerUp" event was
|
|
598
|
+
* triggered by a tap or a drag. If it was
|
|
599
|
+
* dragged, the pressedKnob will be defined.
|
|
600
|
+
*/
|
|
601
|
+
if (this.pressedKnob === undefined) {
|
|
602
|
+
this.onStart();
|
|
603
|
+
this.onEnd(ev);
|
|
604
|
+
}
|
|
605
|
+
} }, ticks.map((tick) => (h("div", { style: tickStyle(tick), role: "presentation", class: {
|
|
501
606
|
'range-tick': true,
|
|
502
607
|
'range-tick-active': tick.active,
|
|
503
608
|
}, part: tick.active ? 'tick-active' : 'tick' }))), h("div", { class: "range-bar-container" }, h("div", { class: "range-bar", role: "presentation", part: "bar" }), h("div", { class: {
|
|
@@ -54,6 +54,33 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|
|
54
54
|
expect(rangeStart).toHaveReceivedEventDetail({ value: 20 });
|
|
55
55
|
expect(rangeEnd).toHaveReceivedEventDetail({ value: 21 });
|
|
56
56
|
});
|
|
57
|
+
test('should emit end event on tap', async ({ page }, testInfo) => {
|
|
58
|
+
testInfo.annotations.push({
|
|
59
|
+
type: 'issue',
|
|
60
|
+
description: 'https://github.com/ionic-team/ionic-framework/issues/28487',
|
|
61
|
+
});
|
|
62
|
+
await page.setContent(`<ion-range aria-label="Range" value="20"></ion-range>`, config);
|
|
63
|
+
const range = page.locator('ion-range');
|
|
64
|
+
const rangeEndSpy = await page.spyOnEvent('ionKnobMoveEnd');
|
|
65
|
+
const rangeBoundingBox = await range.boundingBox();
|
|
66
|
+
/**
|
|
67
|
+
* Coordinates for the click event.
|
|
68
|
+
* These need to be near the end of the range
|
|
69
|
+
* (or anything that isn't the current value).
|
|
70
|
+
*
|
|
71
|
+
* The number 50 is arbitrary, but it should be
|
|
72
|
+
* less than the width of the range.
|
|
73
|
+
*/
|
|
74
|
+
const x = rangeBoundingBox.width - 50;
|
|
75
|
+
// The y coordinate is the middle of the range.
|
|
76
|
+
const y = rangeBoundingBox.height / 2;
|
|
77
|
+
// Click near the end of the range.
|
|
78
|
+
await range.click({
|
|
79
|
+
position: { x, y },
|
|
80
|
+
});
|
|
81
|
+
await rangeEndSpy.next();
|
|
82
|
+
expect(rangeEndSpy.length).toBe(1);
|
|
83
|
+
});
|
|
57
84
|
// TODO FW-2873
|
|
58
85
|
test.skip('should not scroll when the knob is swiped', async ({ page, skip }) => {
|
|
59
86
|
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
package/dist/docs.json
CHANGED
|
@@ -108,7 +108,7 @@ const Checkbox = class {
|
|
|
108
108
|
const mode = getIonMode(this);
|
|
109
109
|
const path = getSVGPath(mode, indeterminate);
|
|
110
110
|
renderHiddenInput(true, el, name, checked ? value : '', disabled);
|
|
111
|
-
return (h(Host, { class: createColorClasses(color, {
|
|
111
|
+
return (h(Host, { "aria-checked": indeterminate ? 'mixed' : `${checked}`, class: createColorClasses(color, {
|
|
112
112
|
[mode]: true,
|
|
113
113
|
'in-item': hostContext('ion-item', el),
|
|
114
114
|
'checkbox-checked': checked,
|