Skip to content

Commit

Permalink
[QDatepicker] add mobile support (#139)
Browse files Browse the repository at this point in the history
  • Loading branch information
cheesytim authored Sep 17, 2021
1 parent 632b0f8 commit 5f8db55
Show file tree
Hide file tree
Showing 25 changed files with 619 additions and 323 deletions.
11 changes: 0 additions & 11 deletions src/normalize.scss
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,6 @@ button::-moz-focus-inner,
border-style: none;
}

/**
* Restore the focus styles unset by the previous rule.
*/

button:-moz-focusring,
[type='button']:-moz-focusring,
[type='reset']:-moz-focusring,
[type='submit']:-moz-focusring {
outline: 1px dotted ButtonText;
}

/**
* Correct the padding in Firefox.
*/
Expand Down
118 changes: 88 additions & 30 deletions src/qComponents/QDatePicker/src/QDatePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<div
ref="root"
class="q-date-picker"
:class="{ 'q-date-picker_ranged': isRanged }"
>
<div
v-if="!isRanged"
Expand All @@ -10,8 +11,9 @@
>
<q-input
ref="reference"
class="q-date-picker__input"
:model-value="displayValue"
:class="['q-date-editor', { 'q-input_focused': state.pickerVisible }]"
:root-class="{ 'q-input_focused': state.pickerVisible }"
:readonly="!editable"
:disabled="isPickerDisabled"
:name="name"
Expand All @@ -21,6 +23,7 @@
@keyup="handleKeyUp"
@input="handleInput"
@change="handleInputDateChange"
@keydown="handleKeyDown"
>
<template #suffix>
<span
Expand All @@ -33,7 +36,7 @@
</div>
<div
v-else
ref="reference"
ref="rangedReference"
:class="rangeClasses"
tabindex="0"
@click="handleRangeClick"
Expand All @@ -44,37 +47,52 @@
>
<input
autocomplete="off"
class="q-range-input"
class="q-date-picker__input"
:placeholder="startPlaceholder || t('QDatePicker.startPlaceholder')"
:value="displayValue && displayValue[0]"
:disabled="isPickerDisabled"
readonly
tabindex="-1"
/>
<slot name="range-separator">
<span class="q-range-separator">{{ rangeSeparator }}</span>
<span class="q-date-picker__range-separator">{{ rangeSeparator }}</span>
</slot>
<input
autocomplete="off"
:placeholder="endPlaceholder || t('QDatePicker.endPlaceholder')"
:value="displayValue && displayValue[1]"
:disabled="isPickerDisabled"
class="q-range-input"
class="q-date-picker__input"
readonly
tabindex="-1"
/>
<span
:class="iconClass"
class="q-input__icon"
class="q-date-picker__suffix"
@click="handleIconClick"
/>
</div>
<teleport
:to="teleportTo || 'body'"
:disabled="!teleportTo"
>
<q-dialog
v-if="isMobileView"
v-model:visible="state.pickerVisible"
prevent-focus-after-closing
@close="closePicker"
>
<component
:is="panelComponent"
ref="panel"
v-model="transformedToDate"
class="q-picker-panel__dialog-view"
@pick="handlePickClick"
/>
</q-dialog>
<transition
name="q-picker-panel_animation"
v-else
name="q-picker-panel-animation"
@after-leave="destroyPopper"
@before-enter="popperInit"
>
Expand Down Expand Up @@ -119,7 +137,9 @@ import { isServer } from '@/qComponents/constants/isServer';
import { getConfig } from '@/qComponents/config';
import { t } from '@/qComponents/locale';
import { notNull, validateArray } from '@/qComponents/helpers';
import { useMediaQuery } from '@/qComponents/hooks';
import QInput from '@/qComponents/QInput';
import QDialog from '@/qComponents/QDialog';
import type { QFormProvider } from '@/qComponents/QForm';
import type { QInputInstance } from '@/qComponents/QInput';
import type { QFormItemProvider } from '@/qComponents/QFormItem';
Expand Down Expand Up @@ -152,7 +172,7 @@ import type {
export default defineComponent({
name: 'QDatePicker',
componentName: 'QDatePicker',
components: { QInput },
components: { QInput, QDialog },
props: {
/**
* one of sugested types
Expand Down Expand Up @@ -311,8 +331,8 @@ export default defineComponent({
const panel = ref<UnwrappedInstance<DatePanelInstance>>(null);
const qForm = inject<Nullable<QFormProvider>>('qForm', null);
const qFormItem = inject<Nullable<QFormItemProvider>>('qFormItem', null);
const reference =
ref<Nullable<UnwrappedInstance<QInputInstance> | HTMLElement>>(null);
const reference = ref<Nullable<UnwrappedInstance<QInputInstance>>>(null);
const rangedReference = ref<Nullable<HTMLElement>>(null);
const state = reactive<QDatePickerState>({
pickerVisible: false,
Expand All @@ -321,6 +341,9 @@ export default defineComponent({
popper: null
});
const isMobileView = useMediaQuery('(max-width: 768px)');
const isTouchMode = useMediaQuery('(pointer: coarse)');
const calcFirstDayOfWeek = computed<number>(() => {
if (isNumber(props.firstDayOfWeek)) return props.firstDayOfWeek;
return getConfig('locale') === 'ru' ? 1 : 0;
Expand Down Expand Up @@ -351,19 +374,13 @@ export default defineComponent({
);
const rangeClasses = computed<Record<string, boolean>>(() => ({
'q-date-editor': true,
'q-range-editor': true,
'q-range-editor_disabled': isPickerDisabled.value,
'q-range-editor_focused': state.pickerVisible
'q-date-picker__range-wrapper': true,
'q-date-picker__range-wrapper_disabled': isPickerDisabled.value,
'q-date-picker__range-wrapper_focused': state.pickerVisible
}));
const isRanged = computed<boolean>(() => props.type.includes('range'));
const iconClass = computed<string>(() => {
if (isPickerDisabled.value) return 'q-icon-lock';
return state.showCloseIcon ? 'q-icon-close' : 'q-icon-calendar';
});
const panelComponent = computed<
| typeof DateRangePanel
| typeof MonthRangePanel
Expand All @@ -390,6 +407,15 @@ export default defineComponent({
return !transformedToDate.value;
});
const iconClass = computed<string>(() => {
if (isPickerDisabled.value) return 'q-icon-lock';
if (isTouchMode.value)
return !isValueEmpty.value && props.clearable
? 'q-icon-close'
: 'q-icon-calendar';
return state.showCloseIcon ? 'q-icon-close' : 'q-icon-calendar';
});
const displayValue = computed<Nullable<string | string[]>>(() => {
let formattedValue: string | number | Date | (string | number | Date)[] =
'';
Expand Down Expand Up @@ -494,20 +520,39 @@ export default defineComponent({
state.userInput = null;
};
const handleKeyDown = (event: KeyboardEvent): void => {
// prevent letters input
if (
event.key.match(
/^(?!Enter$|Escape$|Tab$|Backspace$|ArrowLeft$|ArrowRight$)[^0-9]+/g
)
) {
event.preventDefault();
}
};
const handleKeyUp = (e: KeyboardEvent): void => {
// if user is typing, do not let picker handle key input
if (state.userInput) {
e.stopPropagation();
}
switch (e.key) {
case 'ArrowRight':
case 'ArrowUp':
case 'ArrowLeft':
case 'ArrowDown': {
panel.value?.navigateDropdown(e);
break;
}
case 'ArrowRight':
case 'ArrowLeft': {
const nativeInput = reference.value?.input ?? null;
if (nativeInput !== document.activeElement) {
panel.value?.navigateDropdown(e);
}
break;
}
case 'Escape': {
state.pickerVisible = false;
e.stopPropagation();
Expand Down Expand Up @@ -545,11 +590,9 @@ export default defineComponent({
const popperInit = (): void => {
const panelEl = panel.value?.$el ?? null;
let referenceEl: HTMLElement;
if (reference.value instanceof HTMLElement) {
referenceEl = reference.value;
} else {
referenceEl = reference.value?.$el;
let referenceEl = reference.value?.$el;
if (isRanged.value) {
referenceEl = rangedReference.value;
}
state.popper = createPopper(referenceEl, panelEl, {
Expand Down Expand Up @@ -592,11 +635,16 @@ export default defineComponent({
const handleFocus = (): void => {
if (isPickerDisabled.value) return;
state.pickerVisible = true;
ctx.emit('focus');
if (!transformedToDate.value || Array.isArray(transformedToDate.value))
if (
!transformedToDate.value ||
Array.isArray(transformedToDate.value) ||
isMobileView.value
)
return;
const format = 'dd.MM.yy';
state.userInput = formatToLocalReadableString(
transformedToDate.value,
Expand Down Expand Up @@ -624,7 +672,7 @@ export default defineComponent({
}
};
const handleClose = (): void => {
const closePicker = (): void => {
if (!state.pickerVisible) return;
state.pickerVisible = false;
};
Expand Down Expand Up @@ -672,11 +720,18 @@ export default defineComponent({
}
);
watch(isMobileView, value => {
if (value) {
closePicker();
}
});
onBeforeUnmount(() => destroyPopper());
provide<QDatePickerProvider>('qDatePicker', {
emit: ctx.emit,
firstDayOfWeek: calcFirstDayOfWeek,
isMobileView,
emitChange,
handlePickClick,
type: toRef(props, 'type'),
Expand All @@ -689,6 +744,8 @@ export default defineComponent({
root,
panel,
reference,
rangedReference,
isMobileView,
isRanged,
isPickerDisabled,
calcFirstDayOfWeek,
Expand All @@ -705,9 +762,10 @@ export default defineComponent({
handlePickClick,
handleFocus,
handleInput,
handleKeyDown,
handleMouseEnter,
handleRangeClick,
handleClose,
closePicker,
handleIconClick,
t
};
Expand Down
2 changes: 1 addition & 1 deletion src/qComponents/QDatePicker/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const formatToLocalReadableString = (
};

const calcInputData = (data: string, inputType: string): string => {
const clearVal = data.replace(/ |,|:|\./g, '');
const clearVal = data.replace(/[^0-9]/g, '');
const array = clearVal.split('');
const options = { separator: '.', maxLength: MAX_DATE_INPUT_LENGTH };

Expand Down
1 change: 1 addition & 0 deletions src/qComponents/QDatePicker/src/panel/Date/DatePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ export default defineComponent({
state,
root,
shortcuts: picker.shortcuts,
isMobileView: picker.isMobileView,
datePanel,
panelContentClasses,
currentMonth,
Expand Down
1 change: 1 addition & 0 deletions src/qComponents/QDatePicker/src/panel/Date/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface DatePanelInstance {
state: DatePanelState;
root: Ref<Nullable<HTMLElement>>;
shortcuts: Ref<Nullable<QDatePickerPropShortcuts>>;
isMobileView: Ref<boolean>;
datePanel: Ref<Nullable<HTMLElement>>;
panelContentClasses: ComputedRef<Record<string, boolean>>;
isPeriodTableShown: ComputedRef<boolean>;
Expand Down
10 changes: 5 additions & 5 deletions src/qComponents/QDatePicker/src/panel/DateRange/DateRange.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,12 @@
<button
type="button"
:disabled="!enableMonthArrow"
:class="{
'q-picker-panel__icon-btn_disabled': !enableMonthArrow
}"
class="q-picker-panel__icon-btn q-icon-triangle-right"
@click="handleLeftNextMonthClick"
/>
<button
type="button"
:disabled="!enableYearArrow"
:class="{ 'q-picker-panel__icon-btn_disabled': !enableYearArrow }"
class="q-picker-panel__icon-btn q-icon-double-triangle-right"
@click="handleLeftNextYearClick"
/>
Expand All @@ -65,6 +61,7 @@
/>
</div>
<div
v-if="!isMobileView"
ref="rightPanel"
:class="rightPanelClasses"
>
Expand Down Expand Up @@ -248,6 +245,7 @@ export default defineComponent({
const rightYear = computed<number>(() => state.rightDate.getFullYear());
const enableMonthArrow = computed<boolean>(() => {
if (picker.isMobileView.value) return true;
const nextMonth = (leftMonth.value + 1) % 12;
const yearOffset = leftMonth.value + 1 >= 12 ? 1 : 0;
return (
Expand All @@ -257,6 +255,7 @@ export default defineComponent({
});
const enableYearArrow = computed<boolean>(() => {
if (picker.isMobileView.value) return true;
return Boolean(
rightYear.value * MONTHS_COUNT +
rightMonth.value -
Expand Down Expand Up @@ -488,7 +487,8 @@ export default defineComponent({
handleRightPrevMonthClick,
handleRangeSelecting,
navigateDropdown,
shortcuts: picker.shortcuts
shortcuts: picker.shortcuts,
isMobileView: picker.isMobileView
};
}
});
Expand Down
1 change: 1 addition & 0 deletions src/qComponents/QDatePicker/src/panel/DateRange/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface DateRangePanelInstance {
leftMonth: ComputedRef<number>;
rightMonth: ComputedRef<number>;
shortcuts: Ref<Nullable<QDatePickerPropShortcuts>>;
isMobileView: Ref<boolean>;
handleRangePick: (val: RangePickValue, close?: boolean) => void;
handleShortcutClick: (shortcut: Date) => void;
handleClear: () => void;
Expand Down
Loading

0 comments on commit 5f8db55

Please sign in to comment.