routie dev init since i didn't adhere to any proper guidance up until now

This commit is contained in:
2026-04-29 22:27:29 -06:00
commit e1dabb71e2
15301 changed files with 3562618 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
import type { PropType } from 'vue';
export interface InputAutocompleteProps {
autocomplete: 'suppress' | string | undefined;
name?: string;
}
export declare const makeAutocompleteProps: <Defaults extends {
autocomplete?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
autocomplete: unknown extends Defaults["autocomplete"] ? PropType<string> : {
type: PropType<unknown extends Defaults["autocomplete"] ? string : string | Defaults["autocomplete"]>;
default: unknown extends Defaults["autocomplete"] ? string : string | Defaults["autocomplete"];
};
};
export declare function useAutocomplete(props: InputAutocompleteProps): {
isSuppressing: Readonly<import("vue").Ref<boolean, boolean>>;
fieldAutocomplete: Readonly<import("vue").Ref<string | undefined, string | undefined>>;
fieldName: Readonly<import("vue").Ref<string | undefined, string | undefined>>;
update: () => number;
};
+27
View File
@@ -0,0 +1,27 @@
// Utilities
import { shallowRef, toRef, useId } from 'vue';
import { propsFactory } from "../util/index.js"; // Types
// Types
// Composables
export const makeAutocompleteProps = propsFactory({
autocomplete: String
}, 'autocomplete');
export function useAutocomplete(props) {
const uniqueId = useId();
const reloadTrigger = shallowRef(0);
const isSuppressing = toRef(() => props.autocomplete === 'suppress');
const fieldName = toRef(() => {
if (!props.name) return undefined;
return isSuppressing.value ? `${props.name}-${uniqueId}-${reloadTrigger.value}` : props.name;
});
const fieldAutocomplete = toRef(() => {
return isSuppressing.value ? 'off' : props.autocomplete;
});
return {
isSuppressing,
fieldAutocomplete,
fieldName,
update: () => reloadTrigger.value = new Date().getTime()
};
}
//# sourceMappingURL=autocomplete.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"autocomplete.js","names":["shallowRef","toRef","useId","propsFactory","makeAutocompleteProps","autocomplete","String","useAutocomplete","props","uniqueId","reloadTrigger","isSuppressing","fieldName","name","undefined","value","fieldAutocomplete","update","Date","getTime"],"sources":["../../src/composables/autocomplete.ts"],"sourcesContent":["// Utilities\nimport { shallowRef, toRef, useId } from 'vue'\nimport { propsFactory } from '@/util'\n\n// Types\nimport type { PropType } from 'vue'\n\n// Types\nexport interface InputAutocompleteProps {\n autocomplete: 'suppress' | string | undefined\n name?: string\n}\n\n// Composables\nexport const makeAutocompleteProps = propsFactory({\n autocomplete: String as PropType<'suppress' | string>,\n}, 'autocomplete')\n\nexport function useAutocomplete (props: InputAutocompleteProps) {\n const uniqueId = useId()\n const reloadTrigger = shallowRef(0)\n\n const isSuppressing = toRef(() => props.autocomplete === 'suppress')\n\n const fieldName = toRef(() => {\n if (!props.name) return undefined\n\n return isSuppressing.value\n ? `${props.name}-${uniqueId}-${reloadTrigger.value}`\n : props.name\n })\n\n const fieldAutocomplete = toRef(() => {\n return isSuppressing.value\n ? 'off'\n : props.autocomplete\n })\n\n return {\n isSuppressing,\n fieldAutocomplete,\n fieldName,\n update: () => reloadTrigger.value = new Date().getTime(),\n }\n}\n"],"mappings":"AAAA;AACA,SAASA,UAAU,EAAEC,KAAK,EAAEC,KAAK,QAAQ,KAAK;AAAA,SACrCC,YAAY,4BAErB;AAGA;AAMA;AACA,OAAO,MAAMC,qBAAqB,GAAGD,YAAY,CAAC;EAChDE,YAAY,EAAEC;AAChB,CAAC,EAAE,cAAc,CAAC;AAElB,OAAO,SAASC,eAAeA,CAAEC,KAA6B,EAAE;EAC9D,MAAMC,QAAQ,GAAGP,KAAK,CAAC,CAAC;EACxB,MAAMQ,aAAa,GAAGV,UAAU,CAAC,CAAC,CAAC;EAEnC,MAAMW,aAAa,GAAGV,KAAK,CAAC,MAAMO,KAAK,CAACH,YAAY,KAAK,UAAU,CAAC;EAEpE,MAAMO,SAAS,GAAGX,KAAK,CAAC,MAAM;IAC5B,IAAI,CAACO,KAAK,CAACK,IAAI,EAAE,OAAOC,SAAS;IAEjC,OAAOH,aAAa,CAACI,KAAK,GACtB,GAAGP,KAAK,CAACK,IAAI,IAAIJ,QAAQ,IAAIC,aAAa,CAACK,KAAK,EAAE,GAClDP,KAAK,CAACK,IAAI;EAChB,CAAC,CAAC;EAEF,MAAMG,iBAAiB,GAAGf,KAAK,CAAC,MAAM;IACpC,OAAOU,aAAa,CAACI,KAAK,GACtB,KAAK,GACLP,KAAK,CAACH,YAAY;EACxB,CAAC,CAAC;EAEF,OAAO;IACLM,aAAa;IACbK,iBAAiB;IACjBJ,SAAS;IACTK,MAAM,EAAEA,CAAA,KAAMP,aAAa,CAACK,KAAK,GAAG,IAAIG,IAAI,CAAC,CAAC,CAACC,OAAO,CAAC;EACzD,CAAC;AACH","ignoreList":[]}
+7
View File
@@ -0,0 +1,7 @@
interface AutofocusProps {
autofocus: boolean;
}
export declare function useAutofocus(props: AutofocusProps): {
onIntersect: (isIntersecting: boolean, entries: IntersectionObserverEntry[]) => void;
};
+12
View File
@@ -0,0 +1,12 @@
export function useAutofocus(props) {
function onIntersect(isIntersecting, entries) {
if (!props.autofocus || !isIntersecting) return;
const el = entries[0].target;
const target = el.matches('input,textarea') ? el : el.querySelector('input,textarea');
target?.focus();
}
return {
onIntersect
};
}
//# sourceMappingURL=autofocus.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"autofocus.js","names":["useAutofocus","props","onIntersect","isIntersecting","entries","autofocus","el","target","matches","querySelector","focus"],"sources":["../../src/composables/autofocus.ts"],"sourcesContent":["interface AutofocusProps {\n autofocus: boolean\n}\n\nexport function useAutofocus (props: AutofocusProps) {\n function onIntersect (\n isIntersecting: boolean,\n entries: IntersectionObserverEntry[]\n ) {\n if (!props.autofocus || !isIntersecting) return\n\n const el = entries[0].target\n const target = (el.matches('input,textarea') ? el : el.querySelector('input,textarea')) as HTMLElement | null\n target?.focus()\n }\n\n return {\n onIntersect,\n }\n}\n"],"mappings":"AAIA,OAAO,SAASA,YAAYA,CAAEC,KAAqB,EAAE;EACnD,SAASC,WAAWA,CAClBC,cAAuB,EACvBC,OAAoC,EACpC;IACA,IAAI,CAACH,KAAK,CAACI,SAAS,IAAI,CAACF,cAAc,EAAE;IAEzC,MAAMG,EAAE,GAAGF,OAAO,CAAC,CAAC,CAAC,CAACG,MAAM;IAC5B,MAAMA,MAAM,GAAID,EAAE,CAACE,OAAO,CAAC,gBAAgB,CAAC,GAAGF,EAAE,GAAGA,EAAE,CAACG,aAAa,CAAC,gBAAgB,CAAwB;IAC7GF,MAAM,EAAEG,KAAK,CAAC,CAAC;EACjB;EAEA,OAAO;IACLR;EACF,CAAC;AACH","ignoreList":[]}
+14
View File
@@ -0,0 +1,14 @@
export interface BorderProps {
border?: boolean | number | string;
}
export declare const makeBorderProps: <Defaults extends {
border?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
border: unknown extends Defaults["border"] ? (BooleanConstructor | NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["border"] ? string | number | boolean : string | number | boolean | Defaults["border"]>;
default: unknown extends Defaults["border"] ? string | number | boolean : Defaults["border"] | NonNullable<string | number | boolean>;
};
};
export declare function useBorder(props: BorderProps, name?: string): {
borderClasses: import("vue").ComputedRef<string | string[]>;
};
+22
View File
@@ -0,0 +1,22 @@
// Utilities
import { computed } from 'vue';
import { getCurrentInstanceName, propsFactory } from "../util/index.js"; // Types
// Composables
export const makeBorderProps = propsFactory({
border: [Boolean, Number, String]
}, 'border');
export function useBorder(props, name = getCurrentInstanceName()) {
const borderClasses = computed(() => {
const border = props.border;
if (border === true || border === '') {
return `${name}--border`;
} else if (typeof border === 'string' || border === 0) {
return String(border).split(' ').map(v => `border-${v}`);
}
return [];
});
return {
borderClasses
};
}
//# sourceMappingURL=border.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"border.js","names":["computed","getCurrentInstanceName","propsFactory","makeBorderProps","border","Boolean","Number","String","useBorder","props","name","borderClasses","split","map","v"],"sources":["../../src/composables/border.ts"],"sourcesContent":["// Utilities\nimport { computed } from 'vue'\nimport { getCurrentInstanceName, propsFactory } from '@/util'\n\n// Types\nexport interface BorderProps {\n border?: boolean | number | string\n}\n\n// Composables\nexport const makeBorderProps = propsFactory({\n border: [Boolean, Number, String],\n}, 'border')\n\nexport function useBorder (\n props: BorderProps,\n name = getCurrentInstanceName(),\n) {\n const borderClasses = computed(() => {\n const border = props.border\n\n if (border === true || border === '') {\n return `${name}--border`\n } else if (\n typeof border === 'string' ||\n border === 0\n ) {\n return String(border).split(' ').map(v => `border-${v}`)\n }\n\n return []\n })\n\n return { borderClasses }\n}\n"],"mappings":"AAAA;AACA,SAASA,QAAQ,QAAQ,KAAK;AAAA,SACrBC,sBAAsB,EAAEC,YAAY,4BAE7C;AAKA;AACA,OAAO,MAAMC,eAAe,GAAGD,YAAY,CAAC;EAC1CE,MAAM,EAAE,CAACC,OAAO,EAAEC,MAAM,EAAEC,MAAM;AAClC,CAAC,EAAE,QAAQ,CAAC;AAEZ,OAAO,SAASC,SAASA,CACvBC,KAAkB,EAClBC,IAAI,GAAGT,sBAAsB,CAAC,CAAC,EAC/B;EACA,MAAMU,aAAa,GAAGX,QAAQ,CAAC,MAAM;IACnC,MAAMI,MAAM,GAAGK,KAAK,CAACL,MAAM;IAE3B,IAAIA,MAAM,KAAK,IAAI,IAAIA,MAAM,KAAK,EAAE,EAAE;MACpC,OAAO,GAAGM,IAAI,UAAU;IAC1B,CAAC,MAAM,IACL,OAAON,MAAM,KAAK,QAAQ,IAC1BA,MAAM,KAAK,CAAC,EACZ;MACA,OAAOG,MAAM,CAACH,MAAM,CAAC,CAACQ,KAAK,CAAC,GAAG,CAAC,CAACC,GAAG,CAACC,CAAC,IAAI,UAAUA,CAAC,EAAE,CAAC;IAC1D;IAEA,OAAO,EAAE;EACX,CAAC,CAAC;EAEF,OAAO;IAAEH;EAAc,CAAC;AAC1B","ignoreList":[]}
+160
View File
@@ -0,0 +1,160 @@
import type { PropType } from 'vue';
export interface CalendarProps {
allowedDates: unknown[] | ((date: unknown) => boolean) | undefined;
disabled: boolean;
displayValue?: unknown;
modelValue: unknown[] | undefined;
max: unknown;
min: unknown;
showAdjacentMonths: boolean;
month: number | string | undefined;
weekdays: number[];
year: number | string | undefined;
weeksInMonth: 'dynamic' | 'static';
firstDayOfWeek: number | string | undefined;
firstDayOfYear: number | string | undefined;
weekdayFormat: 'long' | 'short' | 'narrow' | undefined;
'onUpdate:modelValue': ((value: unknown[]) => void) | undefined;
'onUpdate:month': ((value: number) => void) | undefined;
'onUpdate:year': ((value: number) => void) | undefined;
}
export type CalendarDay = {
date: Date;
formatted: string;
isAdjacent: boolean;
isDisabled: boolean;
isEnd: boolean;
isHidden: boolean;
isSame: boolean;
isSelected: boolean;
isStart: boolean;
isToday: boolean;
isWeekEnd: boolean;
isWeekStart: boolean;
isoDate: string;
localized: string;
month: number;
year: number;
};
export type CalendarWeekdays = 0 | 1 | 2 | 3 | 4 | 5 | 6;
export declare const makeCalendarProps: <Defaults extends {
allowedDates?: unknown;
disabled?: unknown;
displayValue?: unknown;
modelValue?: unknown;
month?: unknown;
max?: unknown;
min?: unknown;
showAdjacentMonths?: unknown;
year?: unknown;
weekdays?: unknown;
weeksInMonth?: unknown;
firstDayOfWeek?: unknown;
firstDayOfYear?: unknown;
weekdayFormat?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
allowedDates: unknown extends Defaults["allowedDates"] ? PropType<unknown[] | ((date: unknown) => boolean)> : {
type: PropType<unknown extends Defaults["allowedDates"] ? unknown[] | ((date: unknown) => boolean) : unknown[] | ((date: unknown) => boolean) | Defaults["allowedDates"]>;
default: unknown extends Defaults["allowedDates"] ? unknown[] | ((date: unknown) => boolean) : Defaults["allowedDates"] | NonNullable<unknown[] | ((date: unknown) => boolean)>;
};
disabled: unknown extends Defaults["disabled"] ? {
type: BooleanConstructor;
default: null;
} : Omit<{
type: BooleanConstructor;
default: null;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["disabled"] ? boolean : boolean | Defaults["disabled"]>;
default: unknown extends Defaults["disabled"] ? boolean : boolean | Defaults["disabled"];
};
displayValue: unknown extends Defaults["displayValue"] ? PropType<unknown> : {
type: PropType<unknown extends Defaults["displayValue"] ? unknown : unknown>;
default: unknown extends Defaults["displayValue"] ? unknown : {} | Defaults["displayValue"];
};
modelValue: unknown extends Defaults["modelValue"] ? PropType<unknown[]> : {
type: PropType<unknown extends Defaults["modelValue"] ? unknown[] : unknown[] | Defaults["modelValue"]>;
default: unknown extends Defaults["modelValue"] ? unknown[] : unknown[] | Defaults["modelValue"];
};
month: unknown extends Defaults["month"] ? (NumberConstructor | StringConstructor)[] : {
type: PropType<unknown extends Defaults["month"] ? string | number : string | number | Defaults["month"]>;
default: unknown extends Defaults["month"] ? string | number : Defaults["month"] | NonNullable<string | number>;
};
max: unknown extends Defaults["max"] ? PropType<unknown> : {
type: PropType<unknown extends Defaults["max"] ? unknown : unknown>;
default: unknown extends Defaults["max"] ? unknown : {} | Defaults["max"];
};
min: unknown extends Defaults["min"] ? PropType<unknown> : {
type: PropType<unknown extends Defaults["min"] ? unknown : unknown>;
default: unknown extends Defaults["min"] ? unknown : {} | Defaults["min"];
};
showAdjacentMonths: unknown extends Defaults["showAdjacentMonths"] ? BooleanConstructor : {
type: PropType<unknown extends Defaults["showAdjacentMonths"] ? boolean : boolean | Defaults["showAdjacentMonths"]>;
default: unknown extends Defaults["showAdjacentMonths"] ? boolean : boolean | Defaults["showAdjacentMonths"];
};
year: unknown extends Defaults["year"] ? (NumberConstructor | StringConstructor)[] : {
type: PropType<unknown extends Defaults["year"] ? string | number : string | number | Defaults["year"]>;
default: unknown extends Defaults["year"] ? string | number : Defaults["year"] | NonNullable<string | number>;
};
weekdays: unknown extends Defaults["weekdays"] ? {
type: PropType<CalendarWeekdays[]>;
default: () => number[];
} : Omit<{
type: PropType<CalendarWeekdays[]>;
default: () => number[];
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["weekdays"] ? CalendarWeekdays[] : CalendarWeekdays[] | Defaults["weekdays"]>;
default: unknown extends Defaults["weekdays"] ? CalendarWeekdays[] : CalendarWeekdays[] | Defaults["weekdays"];
};
weeksInMonth: unknown extends Defaults["weeksInMonth"] ? {
type: PropType<'dynamic' | 'static'>;
default: string;
} : Omit<{
type: PropType<'dynamic' | 'static'>;
default: string;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["weeksInMonth"] ? "dynamic" | "static" : "dynamic" | "static" | Defaults["weeksInMonth"]>;
default: unknown extends Defaults["weeksInMonth"] ? "dynamic" | "static" : Defaults["weeksInMonth"] | NonNullable<"dynamic" | "static">;
};
firstDayOfWeek: unknown extends Defaults["firstDayOfWeek"] ? {
type: (NumberConstructor | StringConstructor)[];
default: undefined;
} : Omit<{
type: (NumberConstructor | StringConstructor)[];
default: undefined;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["firstDayOfWeek"] ? string | number : string | number | Defaults["firstDayOfWeek"]>;
default: unknown extends Defaults["firstDayOfWeek"] ? string | number : Defaults["firstDayOfWeek"] | NonNullable<string | number>;
};
firstDayOfYear: unknown extends Defaults["firstDayOfYear"] ? {
type: (NumberConstructor | StringConstructor)[];
default: undefined;
} : Omit<{
type: (NumberConstructor | StringConstructor)[];
default: undefined;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["firstDayOfYear"] ? string | number : string | number | Defaults["firstDayOfYear"]>;
default: unknown extends Defaults["firstDayOfYear"] ? string | number : Defaults["firstDayOfYear"] | NonNullable<string | number>;
};
weekdayFormat: unknown extends Defaults["weekdayFormat"] ? PropType<"long" | "narrow" | "short" | undefined> : {
type: PropType<unknown extends Defaults["weekdayFormat"] ? "long" | "narrow" | "short" | undefined : "long" | "narrow" | "short" | Defaults["weekdayFormat"] | undefined>;
default: unknown extends Defaults["weekdayFormat"] ? "long" | "narrow" | "short" | undefined : Defaults["weekdayFormat"] | NonNullable<"long" | "narrow" | "short" | undefined>;
};
};
export declare function useCalendar(props: CalendarProps): {
displayValue: import("vue").ComputedRef<unknown>;
daysInMonth: import("vue").ComputedRef<CalendarDay[]>;
daysInWeek: import("vue").ComputedRef<CalendarDay[]>;
genDays: (days: Date[], today: Date) => CalendarDay[];
model: import("vue").Ref<readonly unknown[], readonly unknown[]> & {
readonly externalValue: unknown[] | undefined;
};
weeksInMonth: import("vue").ComputedRef<unknown[][]>;
weekdayLabels: import("vue").ComputedRef<string[]>;
weekNumbers: import("vue").ComputedRef<(number | null)[]>;
};
export declare function useCalendarRange(props: Pick<CalendarProps, 'min' | 'max'>): {
minDate: import("vue").ComputedRef<unknown>;
maxDate: import("vue").ComputedRef<unknown>;
clampDate: (date: unknown) => unknown;
isInAllowedRange: (date: unknown) => boolean;
};
+190
View File
@@ -0,0 +1,190 @@
// Composables
import { useDate } from "./date/date.js";
import { useProxiedModel } from "./proxiedModel.js"; // Utilities
import { computed } from 'vue';
import { propsFactory, wrapInArray } from "../util/index.js"; // Types
// Types
// Composables
export const makeCalendarProps = propsFactory({
allowedDates: [Array, Function],
disabled: {
type: Boolean,
default: null
},
displayValue: null,
modelValue: Array,
month: [Number, String],
max: null,
min: null,
showAdjacentMonths: Boolean,
year: [Number, String],
weekdays: {
type: Array,
default: () => [0, 1, 2, 3, 4, 5, 6]
},
weeksInMonth: {
type: String,
default: 'dynamic'
},
firstDayOfWeek: {
type: [Number, String],
default: undefined
},
firstDayOfYear: {
type: [Number, String],
default: undefined
},
weekdayFormat: String
}, 'calendar');
export function useCalendar(props) {
const adapter = useDate();
const model = useProxiedModel(props, 'modelValue', [], v => wrapInArray(v).map(i => adapter.date(i)));
const displayValue = computed(() => {
if (props.displayValue) return adapter.date(props.displayValue);
if (model.value.length > 0) return adapter.date(model.value[0]);
if (props.min) return adapter.date(props.min);
if (Array.isArray(props.allowedDates)) return adapter.date(props.allowedDates[0]);
return adapter.date();
});
const year = useProxiedModel(props, 'year', undefined, v => {
const value = v != null ? Number(v) : adapter.getYear(displayValue.value);
return adapter.startOfYear(adapter.setYear(adapter.date(), value));
}, v => adapter.getYear(v));
const month = useProxiedModel(props, 'month', undefined, v => {
const value = v != null ? Number(v) : adapter.getMonth(displayValue.value);
const date = adapter.setYear(adapter.startOfMonth(adapter.date()), adapter.getYear(year.value));
return adapter.setMonth(date, value);
}, v => adapter.getMonth(v));
const weekdayLabels = computed(() => {
const firstDayOfWeek = adapter.toJsDate(adapter.startOfWeek(adapter.date(), props.firstDayOfWeek)).getDay();
return adapter.getWeekdays(props.firstDayOfWeek, props.weekdayFormat).filter((_, i) => props.weekdays.includes((i + firstDayOfWeek) % 7));
});
const weeksInMonth = computed(() => {
const weeks = adapter.getWeekArray(month.value, props.firstDayOfWeek);
const days = weeks.flat();
// Make sure there's always 6 weeks in month (6 * 7 days)
// if weeksInMonth is 'static'
const daysInMonth = 6 * 7;
if (props.weeksInMonth === 'static' && days.length < daysInMonth) {
const lastDay = days[days.length - 1];
let week = [];
for (let day = 1; day <= daysInMonth - days.length; day++) {
week.push(adapter.addDays(lastDay, day));
if (day % 7 === 0) {
weeks.push(week);
week = [];
}
}
}
return weeks;
});
function genDays(days, today) {
return days.filter(date => {
return props.weekdays.includes(adapter.toJsDate(date).getDay());
}).map((date, index) => {
const isoDate = adapter.toISO(date);
const isAdjacent = !adapter.isSameMonth(date, month.value);
const isStart = adapter.isSameDay(date, adapter.startOfMonth(month.value));
const isEnd = adapter.isSameDay(date, adapter.endOfMonth(month.value));
const isSame = adapter.isSameDay(date, month.value);
const weekdaysCount = props.weekdays.length;
return {
date,
formatted: adapter.format(date, 'keyboardDate'),
isAdjacent,
isDisabled: isDisabled(date),
isEnd,
isHidden: isAdjacent && !props.showAdjacentMonths,
isSame,
isSelected: model.value.some(value => adapter.isSameDay(date, value)),
isStart,
isToday: adapter.isSameDay(date, today),
isWeekEnd: index % weekdaysCount === weekdaysCount - 1,
isWeekStart: index % weekdaysCount === 0,
isoDate,
localized: adapter.format(date, 'dayOfMonth'),
month: adapter.getMonth(date),
year: adapter.getYear(date)
};
});
}
const daysInWeek = computed(() => {
const lastDay = adapter.startOfWeek(displayValue.value, props.firstDayOfWeek);
const week = [];
for (let day = 0; day <= 6; day++) {
week.push(adapter.addDays(lastDay, day));
}
const today = adapter.date();
return genDays(week, today);
});
const daysInMonth = computed(() => {
const days = weeksInMonth.value.flat();
const today = adapter.date();
return genDays(days, today);
});
const weekNumbers = computed(() => {
return weeksInMonth.value.map(week => {
return week.length ? adapter.getWeek(week[0], props.firstDayOfWeek, props.firstDayOfYear) : null;
});
});
const {
minDate,
maxDate
} = useCalendarRange(props);
function isDisabled(value) {
if (props.disabled) return true;
const date = adapter.date(value);
if (minDate.value && adapter.isBefore(adapter.endOfDay(date), minDate.value)) return true;
if (maxDate.value && adapter.isAfter(date, maxDate.value)) return true;
if (Array.isArray(props.allowedDates) && props.allowedDates.length > 0) {
return !props.allowedDates.some(d => adapter.isSameDay(adapter.date(d), date));
}
if (typeof props.allowedDates === 'function') {
return !props.allowedDates(date);
}
return false;
}
return {
displayValue,
daysInMonth,
daysInWeek,
genDays,
model,
weeksInMonth,
weekdayLabels,
weekNumbers
};
}
export function useCalendarRange(props) {
const adapter = useDate();
const minDate = computed(() => {
if (!props.min) return null;
const date = adapter.date(props.min);
return adapter.isValid(date) ? date : null;
});
const maxDate = computed(() => {
if (!props.max) return null;
const date = adapter.date(props.max);
return adapter.isValid(date) ? date : null;
});
function clampDate(date) {
if (minDate.value && adapter.isBefore(date, minDate.value)) {
return minDate.value;
}
if (maxDate.value && adapter.isAfter(date, maxDate.value)) {
return maxDate.value;
}
return date;
}
function isInAllowedRange(date) {
return (!minDate.value || adapter.isAfter(date, minDate.value)) && (!maxDate.value || adapter.isBefore(date, maxDate.value));
}
return {
minDate,
maxDate,
clampDate,
isInAllowedRange
};
}
//# sourceMappingURL=calendar.js.map
File diff suppressed because one or more lines are too long
+26
View File
@@ -0,0 +1,26 @@
import type { CSSProperties, MaybeRefOrGetter, Ref } from 'vue';
export type ColorValue = string | false | null | undefined;
export interface TextColorData {
textColorClasses: Ref<string[]>;
textColorStyles: Ref<CSSProperties>;
}
export interface BackgroundColorData {
backgroundColorClasses: Ref<string[]>;
backgroundColorStyles: Ref<CSSProperties>;
}
export declare function useColor(colors: MaybeRefOrGetter<{
background?: ColorValue;
text?: ColorValue;
}>): {
colorClasses: Readonly<Ref<string[], string[]>>;
colorStyles: Readonly<Ref<CSSProperties, CSSProperties>>;
};
export declare function useTextColor(color: MaybeRefOrGetter<ColorValue>): TextColorData;
export declare function useBackgroundColor(color: MaybeRefOrGetter<ColorValue>): BackgroundColorData;
export declare function computeColor(colors: MaybeRefOrGetter<{
background?: ColorValue;
text?: ColorValue;
}>): {
class: string[];
style: CSSProperties;
};
+77
View File
@@ -0,0 +1,77 @@
// Utilities
import { toValue } from 'vue';
import { destructComputed, hasLightForeground, isCssColor, isParsableColor, parseColor } from "../util/index.js"; // Types
// Composables
export function useColor(colors) {
return destructComputed(() => {
const {
class: colorClasses,
style: colorStyles
} = computeColor(colors);
return {
colorClasses,
colorStyles
};
});
}
export function useTextColor(color) {
const {
colorClasses: textColorClasses,
colorStyles: textColorStyles
} = useColor(() => ({
text: toValue(color)
}));
return {
textColorClasses,
textColorStyles
};
}
export function useBackgroundColor(color) {
const {
colorClasses: backgroundColorClasses,
colorStyles: backgroundColorStyles
} = useColor(() => ({
background: toValue(color)
}));
return {
backgroundColorClasses,
backgroundColorStyles
};
}
function normalizeColors(colors) {
return {
text: typeof colors.text === 'string' ? colors.text.replace(/^text-/, '') : colors.text,
background: typeof colors.background === 'string' ? colors.background.replace(/^bg-/, '') : colors.background
};
}
export function computeColor(colors) {
const _colors = normalizeColors(toValue(colors));
const classes = [];
const styles = {};
if (_colors.background) {
if (isCssColor(_colors.background)) {
styles.backgroundColor = _colors.background;
if (!_colors.text && isParsableColor(_colors.background)) {
const backgroundColor = parseColor(_colors.background);
if (backgroundColor.a == null || backgroundColor.a === 1) {
classes.push(hasLightForeground(backgroundColor) ? 'v-theme-on-dark' : 'v-theme-on-light');
}
}
} else {
classes.push(`bg-${_colors.background}`);
}
}
if (_colors.text) {
if (isCssColor(_colors.text)) {
styles.color = _colors.text;
styles.caretColor = _colors.text;
} else {
classes.push(`text-${_colors.text}`);
}
}
return {
class: classes,
style: styles
};
}
//# sourceMappingURL=color.js.map
File diff suppressed because one or more lines are too long
+25
View File
@@ -0,0 +1,25 @@
import type { PropType, StyleValue } from 'vue';
export type ClassValue = any;
export interface ComponentProps {
class: ClassValue;
style: StyleValue | undefined;
}
export declare const makeComponentProps: <Defaults extends {
class?: unknown;
style?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
class: unknown extends Defaults["class"] ? PropType<any> : {
type: PropType<unknown extends Defaults["class"] ? any : any>;
default: unknown extends Defaults["class"] ? any : any;
};
style: unknown extends Defaults["style"] ? {
type: PropType<StyleValue>;
default: null;
} : Omit<{
type: PropType<StyleValue>;
default: null;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["style"] ? StyleValue : Defaults["style"] | StyleValue>;
default: unknown extends Defaults["style"] ? StyleValue : Defaults["style"] | NonNullable<StyleValue>;
};
};
+13
View File
@@ -0,0 +1,13 @@
// Utilities
import { propsFactory } from "../util/propsFactory.js"; // Types
// TODO: import from vue once upstream PR is merged
// https://github.com/vuejs/core/pull/14441
// Composables
export const makeComponentProps = propsFactory({
class: [String, Array, Object],
style: {
type: [String, Array, Object],
default: null
}
}, 'component');
//# sourceMappingURL=component.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"component.js","names":["propsFactory","makeComponentProps","class","String","Array","Object","style","type","default"],"sources":["../../src/composables/component.ts"],"sourcesContent":["// Utilities\nimport { propsFactory } from '@/util/propsFactory'\n\n// Types\nimport type { PropType, StyleValue } from 'vue'\n\n// TODO: import from vue once upstream PR is merged\n// https://github.com/vuejs/core/pull/14441\nexport type ClassValue = any\n\nexport interface ComponentProps {\n class: ClassValue\n style: StyleValue | undefined\n}\n\n// Composables\nexport const makeComponentProps = propsFactory({\n class: [String, Array, Object] as PropType<ClassValue>,\n style: {\n type: [String, Array, Object] as PropType<StyleValue>,\n default: null,\n },\n}, 'component')\n"],"mappings":"AAAA;AAAA,SACSA,YAAY,mCAErB;AAGA;AACA;AAQA;AACA,OAAO,MAAMC,kBAAkB,GAAGD,YAAY,CAAC;EAC7CE,KAAK,EAAE,CAACC,MAAM,EAAEC,KAAK,EAAEC,MAAM,CAAyB;EACtDC,KAAK,EAAE;IACLC,IAAI,EAAE,CAACJ,MAAM,EAAEC,KAAK,EAAEC,MAAM,CAAyB;IACrDG,OAAO,EAAE;EACX;AACF,CAAC,EAAE,WAAW,CAAC","ignoreList":[]}
+45
View File
@@ -0,0 +1,45 @@
export interface DateAdapter<T = unknown> {
date(value?: any): T | null;
format(date: T, formatString: string): string;
toJsDate(value: T): Date;
parseISO(date: string): T;
toISO(date: T): string;
startOfDay(date: T): T;
endOfDay(date: T): T;
startOfWeek(date: T, firstDayOfWeek?: number | string): T;
endOfWeek(date: T): T;
startOfMonth(date: T): T;
endOfMonth(date: T): T;
startOfYear(date: T): T;
endOfYear(date: T): T;
isAfter(date: T, comparing: T): boolean;
isAfterDay(date: T, comparing: T): boolean;
isSameDay(date: T, comparing: T): boolean;
isSameMonth(date: T, comparing: T): boolean;
isSameYear(date: T, comparing: T): boolean;
isBefore(date: T, comparing: T): boolean;
isEqual(date: T, comparing: T): boolean;
isValid(date: any): boolean;
isWithinRange(date: T, range: [T, T]): boolean;
addMinutes(date: T, amount: number): T;
addHours(date: T, amount: number): T;
addDays(date: T, amount: number): T;
addWeeks(date: T, amount: number): T;
addMonths(date: T, amount: number): T;
getYear(date: T): number;
setYear(date: T, year: number): T;
getDiff(date: T, comparing: T | string, unit?: string): number;
getWeekArray(date: T, firstDayOfWeek?: number | string): T[][];
getWeekdays(firstDayOfWeek?: number | string, weekdayFormat?: 'long' | 'short' | 'narrow'): string[];
getWeek(date: T, firstDayOfWeek?: number | string, firstDayOfYear?: number | string): number;
getMonth(date: T): number;
setMonth(date: T, month: number): T;
getDate(date: T): number;
setDate(date: T, day: number): T;
getNextMonth(date: T): T;
getPreviousMonth(date: T): T;
getHours(date: T): number;
setHours(date: T, hours: number): T;
getMinutes(date: T): number;
setMinutes(date: T, minutes: number): T;
}
+2
View File
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=DateAdapter.js.map
@@ -0,0 +1 @@
{"version":3,"file":"DateAdapter.js","names":[],"sources":["../../../src/composables/date/DateAdapter.ts"],"sourcesContent":["export interface DateAdapter<T = unknown> {\n date (value?: any): T | null\n format (date: T, formatString: string): string\n toJsDate (value: T): Date\n parseISO (date: string): T\n toISO (date: T): string\n\n startOfDay (date: T): T\n endOfDay (date: T): T\n startOfWeek (date: T, firstDayOfWeek?: number | string): T\n endOfWeek (date: T): T\n startOfMonth (date: T): T\n endOfMonth (date: T): T\n startOfYear (date: T): T\n endOfYear (date: T): T\n\n isAfter (date: T, comparing: T): boolean\n isAfterDay(date: T, comparing: T): boolean\n\n isSameDay (date: T, comparing: T): boolean\n isSameMonth (date: T, comparing: T): boolean\n isSameYear(date: T, comparing: T): boolean\n\n isBefore (date: T, comparing: T): boolean\n isEqual (date: T, comparing: T): boolean\n isValid (date: any): boolean\n isWithinRange (date: T, range: [T, T]): boolean\n\n addMinutes (date: T, amount: number): T\n addHours (date: T, amount: number): T\n addDays (date: T, amount: number): T\n addWeeks (date: T, amount: number): T\n addMonths (date: T, amount: number): T\n\n getYear (date: T): number\n setYear (date: T, year: number): T\n getDiff (date: T, comparing: T | string, unit?: string): number\n getWeekArray (date: T, firstDayOfWeek?: number | string): T[][]\n getWeekdays (firstDayOfWeek?: number | string, weekdayFormat?: 'long' | 'short' | 'narrow'): string[]\n getWeek (date: T, firstDayOfWeek?: number | string, firstDayOfYear?: number | string): number\n getMonth (date: T): number\n setMonth (date: T, month: number): T\n getDate (date: T): number\n setDate (date: T, day: number): T\n getNextMonth (date: T): T\n getPreviousMonth(date: T): T\n\n getHours (date: T): number\n setHours (date: T, hours: number): T\n getMinutes (date: T): number\n setMinutes (date: T, minutes: number): T\n}\n"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,54 @@
import { VuetifyDateAdapter } from './vuetify.js';
import type { DateAdapter } from '../index.js';
type CustomDateFormat = Intl.DateTimeFormatOptions | ((date: string, formatString: string, locale: string) => string);
export declare class StringDateAdapter implements DateAdapter<string> {
base: VuetifyDateAdapter;
constructor(options: {
locale: string;
formats?: Record<string, CustomDateFormat>;
});
addDays(date: string, amount: number): string;
addHours(date: string, amount: number): string;
addMinutes(date: string, amount: number): string;
addMonths(date: string, amount: number): string;
addWeeks(date: string, amount: number): string;
date(value?: any): string | null;
endOfDay(date: string): string;
endOfMonth(date: string): string;
endOfWeek(date: string): string;
endOfYear(date: string): string;
format(date: string, formatString: string): string;
getDate(date: string): number;
getDiff(date: string, comparing: string, unit?: string): number;
getHours(date: string): number;
getMinutes(date: string): number;
getMonth(date: string): number;
getWeek(date: string, firstDayOfWeek?: number | string, firstDayOfYear?: number | string): number;
getNextMonth(date: string): string;
getPreviousMonth(date: string): string;
getWeekArray(date: string, firstDayOfWeek?: number | string): string[][];
getWeekdays(firstDayOfWeek?: number | string, weekdayFormat?: 'long' | 'short' | 'narrow'): string[];
getYear(date: string): number;
isAfter(date: string, comparing: string): boolean;
isAfterDay(date: string, comparing: string): boolean;
isBefore(date: string, comparing: string): boolean;
isEqual(date: string, comparing: string): boolean;
isSameDay(date: string, comparing: string): boolean;
isSameMonth(date: string, comparing: string): boolean;
isSameYear(date: string, comparing: string): boolean;
isValid(date: any): boolean;
isWithinRange(date: string, range: [string, string]): boolean;
parseISO(date: string): string;
setDate(date: string, day: number): string;
setHours(date: string, hours: number): string;
setMinutes(date: string, minutes: number): string;
setMonth(date: string, month: number): string;
setYear(date: string, year: number): string;
startOfDay(date: string): string;
startOfMonth(date: string): string;
startOfWeek(date: string, firstDayOfWeek?: number | string): string;
startOfYear(date: string): string;
toISO(date: string): string;
toJsDate(value: string): Date;
}
+146
View File
@@ -0,0 +1,146 @@
// Composables
import { VuetifyDateAdapter } from "./vuetify.js"; // Types
export class StringDateAdapter {
constructor(options) {
this.base = new VuetifyDateAdapter({
locale: options.locale,
formats: options.formats && Object.fromEntries(Object.entries(options.formats).map(([k, v]) => {
return [k, typeof v === 'function' ? (date, ...args) => v(this.base.toISO(date), ...args) : v];
}))
});
}
addDays(date, amount) {
return this.base.toISO(this.base.addDays(this.base.date(date), amount));
}
addHours(date, amount) {
return this.base.toISO(this.base.addHours(this.base.date(date), amount));
}
addMinutes(date, amount) {
return this.base.toISO(this.base.addMinutes(this.base.date(date), amount));
}
addMonths(date, amount) {
return this.base.toISO(this.base.addMonths(this.base.date(date), amount));
}
addWeeks(date, amount) {
return this.base.toISO(this.base.addWeeks(this.base.date(date), amount));
}
date(value) {
return this.base.toISO(this.base.date(value));
}
endOfDay(date) {
return this.base.toISO(this.base.endOfDay(this.base.date(date)));
}
endOfMonth(date) {
return this.base.toISO(this.base.endOfMonth(this.base.date(date)));
}
endOfWeek(date) {
return this.base.toISO(this.base.endOfWeek(this.base.date(date)));
}
endOfYear(date) {
return this.base.toISO(this.base.endOfYear(this.base.date(date)));
}
format(date, formatString) {
return this.base.format(this.base.date(date), formatString);
}
getDate(date) {
return this.base.getDate(this.base.date(date));
}
getDiff(date, comparing, unit) {
return this.base.getDiff(this.base.date(date), comparing, unit);
}
getHours(date) {
return this.base.getHours(this.base.date(date));
}
getMinutes(date) {
return this.base.getMinutes(this.base.date(date));
}
getMonth(date) {
return this.base.getMonth(this.base.date(date));
}
getWeek(date, firstDayOfWeek, firstDayOfYear) {
return this.base.getWeek(this.base.date(date), firstDayOfWeek, firstDayOfYear);
}
getNextMonth(date) {
return this.base.toISO(this.base.getNextMonth(this.base.date(date)));
}
getPreviousMonth(date) {
return this.base.toISO(this.base.getPreviousMonth(this.base.date(date)));
}
getWeekArray(date, firstDayOfWeek) {
return this.base.getWeekArray(this.base.date(date), firstDayOfWeek).map(week => {
return week.map(day => {
return this.base.toISO(day);
});
});
}
getWeekdays(firstDayOfWeek, weekdayFormat) {
return this.base.getWeekdays(firstDayOfWeek, weekdayFormat);
}
getYear(date) {
return this.base.getYear(this.base.date(date));
}
isAfter(date, comparing) {
return this.base.isAfter(this.base.date(date), this.base.date(comparing));
}
isAfterDay(date, comparing) {
return this.base.isAfterDay(this.base.date(date), this.base.date(comparing));
}
isBefore(date, comparing) {
return this.base.isBefore(this.base.date(date), this.base.date(comparing));
}
isEqual(date, comparing) {
return this.base.isEqual(this.base.date(date), this.base.date(comparing));
}
isSameDay(date, comparing) {
return this.base.isSameDay(this.base.date(date), this.base.date(comparing));
}
isSameMonth(date, comparing) {
return this.base.isSameMonth(this.base.date(date), this.base.date(comparing));
}
isSameYear(date, comparing) {
return this.base.isSameYear(this.base.date(date), this.base.date(comparing));
}
isValid(date) {
return this.base.isValid(date);
}
isWithinRange(date, range) {
return this.base.isWithinRange(this.base.date(date), [this.base.date(range[0]), this.base.date(range[1])]);
}
parseISO(date) {
return this.base.toISO(this.base.parseISO(date));
}
setDate(date, day) {
return this.base.toISO(this.base.setDate(this.base.date(date), day));
}
setHours(date, hours) {
return this.base.toISO(this.base.setHours(this.base.date(date), hours));
}
setMinutes(date, minutes) {
return this.base.toISO(this.base.setMinutes(this.base.date(date), minutes));
}
setMonth(date, month) {
return this.base.toISO(this.base.setMonth(this.base.date(date), month));
}
setYear(date, year) {
return this.base.toISO(this.base.setYear(this.base.date(date), year));
}
startOfDay(date) {
return this.base.toISO(this.base.startOfDay(this.base.date(date)));
}
startOfMonth(date) {
return this.base.toISO(this.base.startOfMonth(this.base.date(date)));
}
startOfWeek(date, firstDayOfWeek) {
return this.base.toISO(this.base.startOfWeek(this.base.date(date), firstDayOfWeek));
}
startOfYear(date) {
return this.base.toISO(this.base.startOfYear(this.base.date(date)));
}
toISO(date) {
return this.base.toISO(this.base.date(date));
}
toJsDate(value) {
return this.base.date(value);
}
}
//# sourceMappingURL=string.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,54 @@
import type { DateAdapter } from '../DateAdapter.js';
type CustomDateFormat = Intl.DateTimeFormatOptions | ((date: Date, formatString: string, locale: string) => string);
export declare class VuetifyDateAdapter implements DateAdapter<Date> {
locale: string;
formats?: Record<string, CustomDateFormat>;
constructor(options: {
locale: string;
formats?: Record<string, CustomDateFormat>;
});
date(value?: any): Date | null;
toJsDate(date: Date): Date;
toISO(date: Date): string;
parseISO(date: string): Date;
addMinutes(date: Date, amount: number): Date;
addHours(date: Date, amount: number): Date;
addDays(date: Date, amount: number): Date;
addWeeks(date: Date, amount: number): Date;
addMonths(date: Date, amount: number): Date;
getWeekArray(date: Date, firstDayOfWeek?: number | string): Date[][];
startOfWeek(date: Date, firstDayOfWeek?: number | string): Date;
endOfWeek(date: Date): Date;
startOfMonth(date: Date): Date;
endOfMonth(date: Date): Date;
format(date: Date, formatString: string): string;
isEqual(date: Date, comparing: Date): boolean;
isValid(date: any): boolean;
isWithinRange(date: Date, range: [Date, Date]): boolean;
isAfter(date: Date, comparing: Date): boolean;
isAfterDay(date: Date, comparing: Date): boolean;
isBefore(date: Date, comparing: Date): boolean;
isSameDay(date: Date, comparing: Date): boolean;
isSameMonth(date: Date, comparing: Date): boolean;
isSameYear(date: Date, comparing: Date): boolean;
setMinutes(date: Date, count: number): Date;
setHours(date: Date, count: number): Date;
setMonth(date: Date, count: number): Date;
setDate(date: Date, day: number): Date;
setYear(date: Date, year: number): Date;
getDiff(date: Date, comparing: Date | string, unit?: string): number;
getWeekdays(firstDayOfWeek?: number | string, weekdayFormat?: 'long' | 'short' | 'narrow'): string[];
getYear(date: Date): number;
getMonth(date: Date): number;
getWeek(date: Date, firstDayOfWeek?: number | string, firstDayOfYear?: number | string): number;
getDate(date: Date): number;
getNextMonth(date: Date): Date;
getPreviousMonth(date: Date): Date;
getHours(date: Date): number;
getMinutes(date: Date): number;
startOfDay(date: Date): Date;
endOfDay(date: Date): Date;
startOfYear(date: Date): Date;
endOfYear(date: Date): Date;
}
+690
View File
@@ -0,0 +1,690 @@
// Utilities
import { consoleWarn, createRange, padStart } from "../../../util/index.js"; // Types
function weekInfo(locale) {
// https://simplelocalize.io/data/locales/
// then `new Intl.Locale(...).getWeekInfo()`
const code = locale.slice(-2).toUpperCase();
switch (true) {
case locale === 'GB-alt-variant':
{
return {
firstDay: 0,
firstWeekSize: 4
};
}
case locale === '001':
{
return {
firstDay: 1,
firstWeekSize: 1
};
}
case `AG AS BD BR BS BT BW BZ CA CO DM DO ET GT GU HK HN ID IL IN JM JP KE
KH KR LA MH MM MO MT MX MZ NI NP PA PE PH PK PR PY SA SG SV TH TT TW UM US
VE VI WS YE ZA ZW`.includes(code):
{
return {
firstDay: 0,
firstWeekSize: 1
};
}
case `AI AL AM AR AU AZ BA BM BN BY CL CM CN CR CY EC GE HR KG KZ LB LK LV
MD ME MK MN MY NZ RO RS SI TJ TM TR UA UY UZ VN XK`.includes(code):
{
return {
firstDay: 1,
firstWeekSize: 1
};
}
case `AD AN AT AX BE BG CH CZ DE DK EE ES FI FJ FO FR GB GF GP GR HU IE IS
IT LI LT LU MC MQ NL NO PL RE RU SE SK SM VA`.includes(code):
{
return {
firstDay: 1,
firstWeekSize: 4
};
}
case `AE AF BH DJ DZ EG IQ IR JO KW LY OM QA SD SY`.includes(code):
{
return {
firstDay: 6,
firstWeekSize: 1
};
}
case code === 'MV':
{
return {
firstDay: 5,
firstWeekSize: 1
};
}
case code === 'PT':
{
return {
firstDay: 0,
firstWeekSize: 4
};
}
default:
return null;
}
}
function getWeekArray(date, locale, firstDayOfWeek) {
const weeks = [];
let currentWeek = [];
const firstDayOfMonth = startOfMonth(date);
const lastDayOfMonth = endOfMonth(date);
const first = firstDayOfWeek ?? weekInfo(locale)?.firstDay ?? 0;
const firstDayWeekIndex = (firstDayOfMonth.getDay() - first + 7) % 7;
const lastDayWeekIndex = (lastDayOfMonth.getDay() - first + 7) % 7;
for (let i = 0; i < firstDayWeekIndex; i++) {
const adjacentDay = new Date(firstDayOfMonth);
adjacentDay.setDate(adjacentDay.getDate() - (firstDayWeekIndex - i));
currentWeek.push(adjacentDay);
}
for (let i = 1; i <= lastDayOfMonth.getDate(); i++) {
const day = new Date(date.getFullYear(), date.getMonth(), i);
// Add the day to the current week
currentWeek.push(day);
// If the current week has 7 days, add it to the weeks array and start a new week
if (currentWeek.length === 7) {
weeks.push(currentWeek);
currentWeek = [];
}
}
for (let i = 1; i < 7 - lastDayWeekIndex; i++) {
const adjacentDay = new Date(lastDayOfMonth);
adjacentDay.setDate(adjacentDay.getDate() + i);
currentWeek.push(adjacentDay);
}
if (currentWeek.length > 0) {
weeks.push(currentWeek);
}
return weeks;
}
function startOfWeek(date, locale, firstDayOfWeek) {
let day = (firstDayOfWeek ?? weekInfo(locale)?.firstDay ?? 0) % 7;
// prevent infinite loop
if (![0, 1, 2, 3, 4, 5, 6].includes(day)) {
consoleWarn('Invalid firstDayOfWeek, expected discrete number in range [0-6]');
day = 0;
}
const d = new Date(date);
while (d.getDay() !== day) {
d.setDate(d.getDate() - 1);
}
return d;
}
function endOfWeek(date, locale) {
const d = new Date(date);
const lastDay = ((weekInfo(locale)?.firstDay ?? 0) + 6) % 7;
while (d.getDay() !== lastDay) {
d.setDate(d.getDate() + 1);
}
return d;
}
function startOfMonth(date) {
return new Date(date.getFullYear(), date.getMonth(), 1);
}
function endOfMonth(date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
}
function parseLocalDate(value) {
const parts = value.split('-').map(Number);
// new Date() uses local time zone when passing individual date component values
return new Date(parts[0], parts[1] - 1, parts[2]);
}
const _YYYMMDD = /^([12]\d{3}-([1-9]|0[1-9]|1[0-2])-([1-9]|0[1-9]|[12]\d|3[01]))$/;
function date(value) {
if (value == null) return new Date();
if (value instanceof Date) return value;
if (typeof value === 'string') {
let parsed;
if (_YYYMMDD.test(value)) {
return parseLocalDate(value);
} else {
parsed = Date.parse(value);
}
if (!isNaN(parsed)) return new Date(parsed);
}
return null;
}
const sundayJanuarySecond2000 = new Date(2000, 0, 2);
function getWeekdays(locale, firstDayOfWeek, weekdayFormat) {
const daysFromSunday = firstDayOfWeek ?? weekInfo(locale)?.firstDay ?? 0;
return createRange(7).map(i => {
const weekday = new Date(sundayJanuarySecond2000);
weekday.setDate(sundayJanuarySecond2000.getDate() + daysFromSunday + i);
return new Intl.DateTimeFormat(locale, {
weekday: weekdayFormat ?? 'narrow'
}).format(weekday);
});
}
function format(value, formatString, locale, formats) {
const newDate = date(value) ?? new Date();
const customFormat = formats?.[formatString];
if (typeof customFormat === 'function') {
return customFormat(newDate, formatString, locale);
}
let options = {};
switch (formatString) {
case 'fullDate':
options = {
year: 'numeric',
month: 'short',
day: 'numeric'
};
break;
case 'fullDateWithWeekday':
options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
break;
case 'normalDate':
const day = newDate.getDate();
const month = new Intl.DateTimeFormat(locale, {
month: 'long'
}).format(newDate);
return `${day} ${month}`;
case 'normalDateWithWeekday':
options = {
weekday: 'short',
day: 'numeric',
month: 'short'
};
break;
case 'shortDate':
options = {
month: 'short',
day: 'numeric'
};
break;
case 'year':
options = {
year: 'numeric'
};
break;
case 'month':
options = {
month: 'long'
};
break;
case 'monthShort':
options = {
month: 'short'
};
break;
case 'monthAndYear':
options = {
month: 'long',
year: 'numeric'
};
break;
case 'monthAndDate':
options = {
month: 'long',
day: 'numeric'
};
break;
case 'weekday':
options = {
weekday: 'long'
};
break;
case 'weekdayShort':
options = {
weekday: 'short'
};
break;
case 'dayOfMonth':
return new Intl.NumberFormat(locale).format(newDate.getDate());
case 'hours12h':
options = {
hour: 'numeric',
hour12: true
};
break;
case 'hours24h':
options = {
hour: 'numeric',
hour12: false
};
break;
case 'minutes':
options = {
minute: 'numeric'
};
break;
case 'seconds':
options = {
second: 'numeric'
};
break;
case 'fullTime':
options = {
hour: 'numeric',
minute: 'numeric'
};
break;
case 'fullTime12h':
options = {
hour: 'numeric',
minute: 'numeric',
hour12: true
};
break;
case 'fullTime24h':
options = {
hour: 'numeric',
minute: 'numeric',
hour12: false
};
break;
case 'fullDateTime':
options = {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
};
break;
case 'fullDateTime12h':
options = {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
};
break;
case 'fullDateTime24h':
options = {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: false
};
break;
case 'keyboardDate':
options = {
year: 'numeric',
month: '2-digit',
day: '2-digit'
};
break;
case 'keyboardDateTime':
options = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: 'numeric',
minute: 'numeric'
};
return new Intl.DateTimeFormat(locale, options).format(newDate).replace(/, /g, ' ');
case 'keyboardDateTime12h':
options = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
hour12: true
};
return new Intl.DateTimeFormat(locale, options).format(newDate).replace(/, /g, ' ');
case 'keyboardDateTime24h':
options = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
hour12: false
};
return new Intl.DateTimeFormat(locale, options).format(newDate).replace(/, /g, ' ');
default:
options = customFormat ?? {
timeZone: 'UTC',
timeZoneName: 'short'
};
}
return new Intl.DateTimeFormat(locale, options).format(newDate);
}
function toISO(adapter, value) {
const date = adapter.toJsDate(value);
const year = date.getFullYear();
const month = padStart(String(date.getMonth() + 1), 2, '0');
const day = padStart(String(date.getDate()), 2, '0');
return `${year}-${month}-${day}`;
}
function parseISO(value) {
const [year, month, day] = value.split('-').map(Number);
return new Date(year, month - 1, day);
}
function addMinutes(date, amount) {
const d = new Date(date);
d.setMinutes(d.getMinutes() + amount);
return d;
}
function addHours(date, amount) {
const d = new Date(date);
d.setHours(d.getHours() + amount);
return d;
}
function addDays(date, amount) {
const d = new Date(date);
d.setDate(d.getDate() + amount);
return d;
}
function addWeeks(date, amount) {
const d = new Date(date);
d.setDate(d.getDate() + amount * 7);
return d;
}
function addMonths(date, amount) {
const d = new Date(date);
d.setDate(1);
d.setMonth(d.getMonth() + amount);
return d;
}
function getYear(date) {
return date.getFullYear();
}
function getMonth(date) {
return date.getMonth();
}
function getWeek(date, locale, firstDayOfWeek, firstDayOfYear) {
const weekInfoFromLocale = weekInfo(locale);
const weekStart = firstDayOfWeek ?? weekInfoFromLocale?.firstDay ?? 0;
const minWeekSize = weekInfoFromLocale?.firstWeekSize ?? 1;
return firstDayOfYear !== undefined ? calculateWeekWithFirstDayOfYear(date, locale, weekStart, firstDayOfYear) : calculateWeekWithMinWeekSize(date, locale, weekStart, minWeekSize);
}
function calculateWeekWithFirstDayOfYear(date, locale, weekStart, firstDayOfYear) {
const firstDayOfYearOffset = (7 + firstDayOfYear - weekStart) % 7;
const currentWeekStart = startOfWeek(date, locale, weekStart);
const currentWeekEnd = addDays(currentWeekStart, 6);
function yearStartWeekdayOffset(year) {
return (7 + new Date(year, 0, 1).getDay() - weekStart) % 7;
}
let year = getYear(currentWeekStart);
if (year < getYear(currentWeekEnd) && yearStartWeekdayOffset(year + 1) <= firstDayOfYearOffset) {
year++;
}
const yearStart = new Date(year, 0, 1);
const offset = yearStartWeekdayOffset(year);
const d1w1 = offset <= firstDayOfYearOffset ? addDays(yearStart, -offset) : addDays(yearStart, 7 - offset);
return 1 + getDiff(endOfDay(currentWeekStart), startOfDay(d1w1), 'weeks');
}
function calculateWeekWithMinWeekSize(date, locale, weekStart, minWeekSize) {
const currentWeekStart = startOfWeek(date, locale, weekStart);
const currentWeekEnd = addDays(startOfWeek(date, locale, weekStart), 6);
function firstWeekSize(year) {
const yearStart = new Date(year, 0, 1);
return 7 - getDiff(yearStart, startOfWeek(yearStart, locale, weekStart), 'days');
}
let year = getYear(currentWeekStart);
if (year < getYear(currentWeekEnd) && firstWeekSize(year + 1) >= minWeekSize) {
year++;
}
const yearStart = new Date(year, 0, 1);
const size = firstWeekSize(year);
const d1w1 = size >= minWeekSize ? addDays(yearStart, size - 7) : addDays(yearStart, size);
return 1 + getDiff(endOfDay(currentWeekStart), startOfDay(d1w1), 'weeks');
}
function getDate(date) {
return date.getDate();
}
function getNextMonth(date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 1);
}
function getPreviousMonth(date) {
return new Date(date.getFullYear(), date.getMonth() - 1, 1);
}
function getHours(date) {
return date.getHours();
}
function getMinutes(date) {
return date.getMinutes();
}
function startOfYear(date) {
return new Date(date.getFullYear(), 0, 1);
}
function endOfYear(date) {
return new Date(date.getFullYear(), 11, 31);
}
function isWithinRange(date, range) {
return isEqual(date, range[0]) || isEqual(date, range[1]) || isAfter(date, range[0]) && isBefore(date, range[1]);
}
function isValid(date) {
const d = new Date(date);
return d instanceof Date && !isNaN(d.getTime());
}
function isAfter(date, comparing) {
return date.getTime() > comparing.getTime();
}
function isAfterDay(date, comparing) {
return isAfter(startOfDay(date), startOfDay(comparing));
}
function isBefore(date, comparing) {
return date.getTime() < comparing.getTime();
}
function isEqual(date, comparing) {
return date.getTime() === comparing.getTime();
}
function isSameDay(date, comparing) {
return date.getDate() === comparing.getDate() && date.getMonth() === comparing.getMonth() && date.getFullYear() === comparing.getFullYear();
}
function isSameMonth(date, comparing) {
return date.getMonth() === comparing.getMonth() && date.getFullYear() === comparing.getFullYear();
}
function isSameYear(date, comparing) {
return date.getFullYear() === comparing.getFullYear();
}
function getDiff(date, comparing, unit) {
const d = new Date(date);
const c = new Date(comparing);
switch (unit) {
case 'years':
return d.getFullYear() - c.getFullYear();
case 'quarters':
return Math.floor((d.getMonth() - c.getMonth() + (d.getFullYear() - c.getFullYear()) * 12) / 4);
case 'months':
return d.getMonth() - c.getMonth() + (d.getFullYear() - c.getFullYear()) * 12;
case 'weeks':
return Math.floor((d.getTime() - c.getTime()) / (1000 * 60 * 60 * 24 * 7));
case 'days':
return Math.floor((d.getTime() - c.getTime()) / (1000 * 60 * 60 * 24));
case 'hours':
return Math.floor((d.getTime() - c.getTime()) / (1000 * 60 * 60));
case 'minutes':
return Math.floor((d.getTime() - c.getTime()) / (1000 * 60));
case 'seconds':
return Math.floor((d.getTime() - c.getTime()) / 1000);
default:
{
return d.getTime() - c.getTime();
}
}
}
function setHours(date, count) {
const d = new Date(date);
d.setHours(count);
return d;
}
function setMinutes(date, count) {
const d = new Date(date);
d.setMinutes(count);
return d;
}
function setMonth(date, count) {
const d = new Date(date);
d.setMonth(count);
return d;
}
function setDate(date, day) {
const d = new Date(date);
d.setDate(day);
return d;
}
function setYear(date, year) {
const d = new Date(date);
d.setFullYear(year);
return d;
}
function startOfDay(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
}
function endOfDay(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
}
export class VuetifyDateAdapter {
constructor(options) {
this.locale = options.locale;
this.formats = options.formats;
}
date(value) {
return date(value);
}
toJsDate(date) {
return date;
}
toISO(date) {
return toISO(this, date);
}
parseISO(date) {
return parseISO(date);
}
addMinutes(date, amount) {
return addMinutes(date, amount);
}
addHours(date, amount) {
return addHours(date, amount);
}
addDays(date, amount) {
return addDays(date, amount);
}
addWeeks(date, amount) {
return addWeeks(date, amount);
}
addMonths(date, amount) {
return addMonths(date, amount);
}
getWeekArray(date, firstDayOfWeek) {
const firstDay = firstDayOfWeek !== undefined ? Number(firstDayOfWeek) : undefined;
return getWeekArray(date, this.locale, firstDay);
}
startOfWeek(date, firstDayOfWeek) {
const firstDay = firstDayOfWeek !== undefined ? Number(firstDayOfWeek) : undefined;
return startOfWeek(date, this.locale, firstDay);
}
endOfWeek(date) {
return endOfWeek(date, this.locale);
}
startOfMonth(date) {
return startOfMonth(date);
}
endOfMonth(date) {
return endOfMonth(date);
}
format(date, formatString) {
return format(date, formatString, this.locale, this.formats);
}
isEqual(date, comparing) {
return isEqual(date, comparing);
}
isValid(date) {
return isValid(date);
}
isWithinRange(date, range) {
return isWithinRange(date, range);
}
isAfter(date, comparing) {
return isAfter(date, comparing);
}
isAfterDay(date, comparing) {
return isAfterDay(date, comparing);
}
isBefore(date, comparing) {
return !isAfter(date, comparing) && !isEqual(date, comparing);
}
isSameDay(date, comparing) {
return isSameDay(date, comparing);
}
isSameMonth(date, comparing) {
return isSameMonth(date, comparing);
}
isSameYear(date, comparing) {
return isSameYear(date, comparing);
}
setMinutes(date, count) {
return setMinutes(date, count);
}
setHours(date, count) {
return setHours(date, count);
}
setMonth(date, count) {
return setMonth(date, count);
}
setDate(date, day) {
return setDate(date, day);
}
setYear(date, year) {
return setYear(date, year);
}
getDiff(date, comparing, unit) {
return getDiff(date, comparing, unit);
}
getWeekdays(firstDayOfWeek, weekdayFormat) {
const firstDay = firstDayOfWeek !== undefined ? Number(firstDayOfWeek) : undefined;
return getWeekdays(this.locale, firstDay, weekdayFormat);
}
getYear(date) {
return getYear(date);
}
getMonth(date) {
return getMonth(date);
}
getWeek(date, firstDayOfWeek, firstDayOfYear) {
const firstDay = firstDayOfWeek !== undefined ? Number(firstDayOfWeek) : undefined;
const firstWeekStart = firstDayOfYear !== undefined ? Number(firstDayOfYear) : undefined;
return getWeek(date, this.locale, firstDay, firstWeekStart);
}
getDate(date) {
return getDate(date);
}
getNextMonth(date) {
return getNextMonth(date);
}
getPreviousMonth(date) {
return getPreviousMonth(date);
}
getHours(date) {
return getHours(date);
}
getMinutes(date) {
return getMinutes(date);
}
startOfDay(date) {
return startOfDay(date);
}
endOfDay(date) {
return endOfDay(date);
}
startOfYear(date) {
return startOfYear(date);
}
endOfYear(date) {
return endOfYear(date);
}
}
//# sourceMappingURL=vuetify.js.map
File diff suppressed because one or more lines are too long
+76
View File
@@ -0,0 +1,76 @@
import type { InjectionKey } from 'vue';
import type { DateAdapter } from './DateAdapter.js';
import type { LocaleInstance } from '../locale.js';
export interface DateInstance extends DateModule.InternalAdapter {
locale?: any;
}
/** Supports module augmentation to specify date adapter types */
export declare namespace DateModule {
interface Adapter {
}
export type InternalAdapter = {} extends Adapter ? DateAdapter : Adapter;
}
export type InternalDateOptions = {
adapter: (new (options: {
locale: any;
formats?: any;
}) => DateInstance) | DateInstance;
formats?: Record<string, any>;
locale: Record<string, any>;
};
export type DateOptions = Partial<InternalDateOptions>;
export declare const DateOptionsSymbol: InjectionKey<InternalDateOptions>;
export declare const DateAdapterSymbol: InjectionKey<DateInstance>;
export declare function createDate(options: DateOptions | undefined, locale: LocaleInstance): {
options: InternalDateOptions;
instance: {
date: (value?: any) => unknown;
format: (date: unknown, formatString: string) => string;
toJsDate: (value: unknown) => Date;
parseISO: (date: string) => unknown;
toISO: (date: unknown) => string;
startOfDay: (date: unknown) => unknown;
endOfDay: (date: unknown) => unknown;
startOfWeek: (date: unknown, firstDayOfWeek?: number | string) => unknown;
endOfWeek: (date: unknown) => unknown;
startOfMonth: (date: unknown) => unknown;
endOfMonth: (date: unknown) => unknown;
startOfYear: (date: unknown) => unknown;
endOfYear: (date: unknown) => unknown;
isAfter: (date: unknown, comparing: unknown) => boolean;
isAfterDay: (date: unknown, comparing: unknown) => boolean;
isSameDay: (date: unknown, comparing: unknown) => boolean;
isSameMonth: (date: unknown, comparing: unknown) => boolean;
isSameYear: (date: unknown, comparing: unknown) => boolean;
isBefore: (date: unknown, comparing: unknown) => boolean;
isEqual: (date: unknown, comparing: unknown) => boolean;
isValid: (date: any) => boolean;
isWithinRange: (date: unknown, range: [unknown, unknown]) => boolean;
addMinutes: (date: unknown, amount: number) => unknown;
addHours: (date: unknown, amount: number) => unknown;
addDays: (date: unknown, amount: number) => unknown;
addWeeks: (date: unknown, amount: number) => unknown;
addMonths: (date: unknown, amount: number) => unknown;
getYear: (date: unknown) => number;
setYear: (date: unknown, year: number) => unknown;
getDiff: (date: unknown, comparing: unknown, unit?: string) => number;
getWeekArray: (date: unknown, firstDayOfWeek?: number | string) => unknown[][];
getWeekdays: (firstDayOfWeek?: number | string, weekdayFormat?: 'long' | 'short' | 'narrow') => string[];
getWeek: (date: unknown, firstDayOfWeek?: number | string, firstDayOfYear?: number | string) => number;
getMonth: (date: unknown) => number;
setMonth: (date: unknown, month: number) => unknown;
getDate: (date: unknown) => number;
setDate: (date: unknown, day: number) => unknown;
getNextMonth: (date: unknown) => unknown;
getPreviousMonth: (date: unknown) => unknown;
getHours: (date: unknown) => number;
setHours: (date: unknown, hours: number) => unknown;
getMinutes: (date: unknown) => number;
setMinutes: (date: unknown, minutes: number) => unknown;
locale?: any;
};
};
export declare function createDateRange(adapter: DateInstance, start: unknown, stop?: unknown): unknown[];
export declare function daysDiff(adapter: DateInstance, start: unknown, stop?: unknown): number;
export declare function useDate(): DateInstance;
+98
View File
@@ -0,0 +1,98 @@
// Composables
import { useLocale } from "../locale.js"; // Utilities
import { inject, reactive, watch } from 'vue';
import { mergeDeep } from "../../util/index.js"; // Types
// Adapters
import { VuetifyDateAdapter } from "./adapters/vuetify.js";
/** Supports module augmentation to specify date adapter types */
export let DateModule;
export const DateOptionsSymbol = Symbol.for('vuetify:date-options');
export const DateAdapterSymbol = Symbol.for('vuetify:date-adapter');
export function createDate(options, locale) {
const _options = mergeDeep({
adapter: VuetifyDateAdapter,
locale: {
af: 'af-ZA',
// ar: '', # not the same value for all variants
bg: 'bg-BG',
ca: 'ca-ES',
ckb: '',
cs: 'cs-CZ',
de: 'de-DE',
el: 'el-GR',
en: 'en-US',
// es: '', # not the same value for all variants
et: 'et-EE',
fa: 'fa-IR',
fi: 'fi-FI',
// fr: '', #not the same value for all variants
hr: 'hr-HR',
hu: 'hu-HU',
he: 'he-IL',
id: 'id-ID',
it: 'it-IT',
ja: 'ja-JP',
ko: 'ko-KR',
lv: 'lv-LV',
lt: 'lt-LT',
nl: 'nl-NL',
no: 'no-NO',
pl: 'pl-PL',
pt: 'pt-PT',
ro: 'ro-RO',
ru: 'ru-RU',
sk: 'sk-SK',
sl: 'sl-SI',
srCyrl: 'sr-SP',
srLatn: 'sr-SP',
sv: 'sv-SE',
th: 'th-TH',
tr: 'tr-TR',
az: 'az-AZ',
uk: 'uk-UA',
vi: 'vi-VN',
zhHans: 'zh-CN',
zhHant: 'zh-TW'
}
}, options);
return {
options: _options,
instance: createInstance(_options, locale)
};
}
export function createDateRange(adapter, start, stop) {
const diff = daysDiff(adapter, start, stop);
const datesInRange = [start];
for (let i = 1; i < diff; i++) {
const nextDate = adapter.addDays(start, i);
datesInRange.push(nextDate);
}
if (stop) {
datesInRange.push(adapter.endOfDay(stop));
}
return datesInRange;
}
export function daysDiff(adapter, start, stop) {
const iso = [`${adapter.toISO(stop ?? start).split('T')[0]}T00:00:00Z`, `${adapter.toISO(start).split('T')[0]}T00:00:00Z`];
return typeof adapter.date() === 'string' ? adapter.getDiff(iso[0], iso[1], 'days') // for StringDateAdapter
: adapter.getDiff(adapter.date(iso[0]), adapter.date(iso[1]), 'days');
}
function createInstance(options, locale) {
const instance = reactive(typeof options.adapter === 'function'
// eslint-disable-next-line new-cap
? new options.adapter({
locale: options.locale[locale.current.value] ?? locale.current.value,
formats: options.formats
}) : options.adapter);
watch(locale.current, value => {
instance.locale = options.locale[value] ?? value ?? instance.locale;
});
return instance;
}
export function useDate() {
const options = inject(DateOptionsSymbol);
if (!options) throw new Error('[Vuetify] Could not find injected date options');
const locale = useLocale();
return createInstance(options, locale);
}
//# sourceMappingURL=date.js.map
File diff suppressed because one or more lines are too long
+4
View File
@@ -0,0 +1,4 @@
export { createDate, useDate, DateAdapterSymbol } from './date.js';
export type { DateAdapter } from './DateAdapter.js';
export type { DateOptions, DateInstance, DateModule } from './date.js';
export { VuetifyDateAdapter } from './adapters/vuetify.js';
+3
View File
@@ -0,0 +1,3 @@
export { createDate, useDate, DateAdapterSymbol } from "./date.js";
export { VuetifyDateAdapter } from "./adapters/vuetify.js";
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","names":["createDate","useDate","DateAdapterSymbol","VuetifyDateAdapter"],"sources":["../../../src/composables/date/index.ts"],"sourcesContent":["export { createDate, useDate, DateAdapterSymbol } from './date'\nexport type { DateAdapter } from './DateAdapter'\nexport type { DateOptions, DateInstance, DateModule } from './date'\nexport { VuetifyDateAdapter } from './adapters/vuetify'\n"],"mappings":"SAASA,UAAU,EAAEC,OAAO,EAAEC,iBAAiB;AAAA,SAGtCC,kBAAkB","ignoreList":[]}
+24
View File
@@ -0,0 +1,24 @@
import type { Ref } from 'vue';
export interface DateFormatProps {
inputFormat?: string;
}
export declare const makeDateFormatProps: <Defaults extends {
inputFormat?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
inputFormat: unknown extends Defaults["inputFormat"] ? {
type: StringConstructor;
validator: (v: string) => boolean;
} : Omit<{
type: StringConstructor;
validator: (v: string) => boolean;
}, "default" | "type"> & {
type: import("vue").PropType<unknown extends Defaults["inputFormat"] ? string : string | Defaults["inputFormat"]>;
default: unknown extends Defaults["inputFormat"] ? string : string | Defaults["inputFormat"];
};
};
export declare function useDateFormat(props: DateFormatProps, locale: Ref<string>): {
isValid: (text: string) => boolean;
parseDate: (dateString: string) => unknown;
formatDate: (value: unknown) => string;
parserFormat: Readonly<Ref<string, string>>;
};
+112
View File
@@ -0,0 +1,112 @@
// Composables
import { useDate } from "./date/date.js"; // Utilities
import { toRef } from 'vue';
import { consoleWarn, propsFactory } from "../util/index.js"; // Types
// Types
class DateFormatSpec {
constructor(order,
// mdy | dmy | ymd
separator // / | - | .
) {
this.order = order;
this.separator = separator;
}
get format() {
return this.order.split('').map(sign => `${sign}${sign}`).join(this.separator).replace('yy', 'yyyy');
}
static canBeParsed(v) {
if (typeof v !== 'string') return false;
const lowercase = v.toLowerCase();
return ['y', 'm', 'd'].every(sign => lowercase.includes(sign)) && ['/', '-', '.'].some(sign => v.includes(sign));
}
static parse(v) {
if (!DateFormatSpec.canBeParsed(v)) {
throw new Error(`[${v}] cannot be parsed into date format specification`);
}
const order = v.toLowerCase().split('').filter((c, i, all) => 'dmy'.includes(c) && all.indexOf(c) === i).join('');
const separator = ['/', '-', '.'].find(sign => v.includes(sign));
return new DateFormatSpec(order, separator);
}
}
export const makeDateFormatProps = propsFactory({
inputFormat: {
type: String,
validator: v => !v || DateFormatSpec.canBeParsed(v)
}
}, 'date-format');
export function useDateFormat(props, locale) {
const adapter = useDate();
function inferFromLocale() {
const localeForDateFormat = locale.value ?? 'en-US';
const formatFromLocale = Intl.DateTimeFormat(localeForDateFormat, {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(adapter.toJsDate(adapter.parseISO('1999-12-07'))).replace(/(07)|(٠٧)|(٢٩)|(۱۶)|(০৭)/, 'dd').replace(/(12)|(١٢)|(٠٨)|(۰۹)|(১২)/, 'mm').replace(/(1999)|(2542)|(١٩٩٩)|(١٤٢٠)|(۱۳۷۸)|(১৯৯৯)/, 'yyyy').replace(/[^ymd\-/.]/g, '').replace(/\.$/, '');
if (!DateFormatSpec.canBeParsed(formatFromLocale)) {
consoleWarn(`Date format inferred from locale [${localeForDateFormat}] is invalid: [${formatFromLocale}]`);
return 'mm/dd/yyyy';
}
return formatFromLocale;
}
const currentFormat = toRef(() => {
return DateFormatSpec.canBeParsed(props.inputFormat) ? DateFormatSpec.parse(props.inputFormat) : DateFormatSpec.parse(inferFromLocale());
});
function parseDate(dateString) {
function parseDateParts(text) {
const parts = text.trim().split(currentFormat.value.separator);
return {
y: Number(parts[currentFormat.value.order.indexOf('y')]),
m: Number(parts[currentFormat.value.order.indexOf('m')]),
d: Number(parts[currentFormat.value.order.indexOf('d')])
};
}
function validateDateParts(dateParts) {
const {
y: year,
m: month,
d: day
} = dateParts;
if (!year || !month || !day) return null;
if (month < 1 || month > 12) return null;
if (day < 1 || day > 31) return null;
return {
year: autoFixYear(year),
month,
day
};
}
function autoFixYear(year) {
const currentYear = adapter.getYear(adapter.date());
if (year > 100 || currentYear % 100 >= 50) {
return year;
}
const currentCentury = ~~(currentYear / 100) * 100;
return year < 50 ? currentCentury + year : currentCentury - 100 + year;
}
const dateParts = parseDateParts(dateString);
const validatedParts = validateDateParts(dateParts);
if (!validatedParts) return null;
const {
year,
month,
day
} = validatedParts;
const pad = v => String(v).padStart(2, '0');
return adapter.parseISO(`${year}-${pad(month)}-${pad(day)}`);
}
function isValid(text) {
return !!parseDate(text);
}
function formatDate(value) {
const parts = adapter.toISO(value).split('T')[0].split('-');
return currentFormat.value.order.split('').map(sign => parts['ymd'.indexOf(sign)]).join(currentFormat.value.separator);
}
return {
isValid,
parseDate,
formatDate,
parserFormat: toRef(() => currentFormat.value.format)
};
}
//# sourceMappingURL=dateFormat.js.map
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
import type { ComputedRef, InjectionKey, Ref } from 'vue';
import type { MaybeRef } from '../util/index.js';
export type DefaultsInstance = undefined | {
[key: string]: undefined | Record<string, unknown>;
global?: Record<string, unknown>;
};
export type DefaultsOptions = Partial<DefaultsInstance>;
export declare const DefaultsSymbol: InjectionKey<Ref<DefaultsInstance>>;
export declare function createDefaults(options?: DefaultsInstance): Ref<DefaultsInstance>;
export declare function injectDefaults(): Ref<DefaultsInstance, DefaultsInstance>;
export declare function provideDefaults(defaults?: MaybeRef<DefaultsInstance | undefined>, options?: {
disabled?: MaybeRef<boolean | undefined>;
reset?: MaybeRef<number | string | undefined>;
root?: MaybeRef<boolean | string | undefined>;
scoped?: MaybeRef<boolean | undefined>;
}): ComputedRef<DefaultsInstance>;
export declare function internalUseDefaults(props?: Record<string, any>, name?: string, defaults?: Ref<DefaultsInstance, DefaultsInstance>): {
props: Record<string, any>;
provideSubDefaults: () => void;
};
export declare function useDefaults<T extends Record<string, any>>(props: T, name?: string): T;
export declare function useDefaults(props?: undefined, name?: string): Record<string, any>;
+101
View File
@@ -0,0 +1,101 @@
// Utilities
import { computed, inject, provide, ref, shallowRef, unref, watchEffect } from 'vue';
import { getCurrentInstance } from "../util/getCurrentInstance.js";
import { mergeDeep, toKebabCase } from "../util/helpers.js";
import { injectSelf } from "../util/injectSelf.js"; // Types
export const DefaultsSymbol = Symbol.for('vuetify:defaults');
export function createDefaults(options) {
return ref(options);
}
export function injectDefaults() {
const defaults = inject(DefaultsSymbol);
if (!defaults) throw new Error('[Vuetify] Could not find defaults instance');
return defaults;
}
export function provideDefaults(defaults, options) {
const injectedDefaults = injectDefaults();
const providedDefaults = ref(defaults);
const newDefaults = computed(() => {
const disabled = unref(options?.disabled);
if (disabled) return injectedDefaults.value;
const scoped = unref(options?.scoped);
const reset = unref(options?.reset);
const root = unref(options?.root);
if (providedDefaults.value == null && !(scoped || reset || root)) return injectedDefaults.value;
let properties = mergeDeep(providedDefaults.value, {
prev: injectedDefaults.value
});
if (scoped) return properties;
if (reset || root) {
const len = Number(reset || Infinity);
for (let i = 0; i <= len; i++) {
if (!properties || !('prev' in properties)) {
break;
}
properties = properties.prev;
}
if (properties && typeof root === 'string' && root in properties) {
properties = mergeDeep(mergeDeep(properties, {
prev: properties
}), properties[root]);
}
return properties;
}
return properties.prev ? mergeDeep(properties.prev, properties, undefined, (_, v) => v !== undefined) : properties;
});
provide(DefaultsSymbol, newDefaults);
return newDefaults;
}
function propIsDefined(vnode, prop) {
return vnode.props && (typeof vnode.props[prop] !== 'undefined' || typeof vnode.props[toKebabCase(prop)] !== 'undefined');
}
export function internalUseDefaults(props = {}, name, defaults = injectDefaults()) {
const vm = getCurrentInstance('useDefaults');
name = name ?? vm.type.name ?? vm.type.__name;
if (!name) {
throw new Error('[Vuetify] Could not determine component name');
}
const componentDefaults = computed(() => defaults.value?.[props._as ?? name]);
const _props = new Proxy(props, {
get(target, prop) {
const propValue = Reflect.get(target, prop);
if (prop === 'class' || prop === 'style') {
return [componentDefaults.value?.[prop], propValue].filter(v => v != null);
}
if (propIsDefined(vm.vnode, prop)) return propValue;
const _componentDefault = componentDefaults.value?.[prop];
if (_componentDefault !== undefined) return _componentDefault;
const _globalDefault = defaults.value?.global?.[prop];
if (_globalDefault !== undefined) return _globalDefault;
return propValue;
}
});
const _subcomponentDefaults = shallowRef();
watchEffect(() => {
if (componentDefaults.value) {
const subComponents = Object.entries(componentDefaults.value).filter(([key]) => key.startsWith(key[0].toUpperCase()));
_subcomponentDefaults.value = subComponents.length ? Object.fromEntries(subComponents) : undefined;
} else {
_subcomponentDefaults.value = undefined;
}
});
function provideSubDefaults() {
const injected = injectSelf(DefaultsSymbol, vm);
provide(DefaultsSymbol, computed(() => {
return _subcomponentDefaults.value ? mergeDeep(injected?.value ?? {}, _subcomponentDefaults.value) : injected?.value;
}));
}
return {
props: _props,
provideSubDefaults
};
}
export function useDefaults(props = {}, name) {
const {
props: _props,
provideSubDefaults
} = internalUseDefaults(props, name);
provideSubDefaults();
return _props;
}
//# sourceMappingURL=defaults.js.map
File diff suppressed because one or more lines are too long
+24
View File
@@ -0,0 +1,24 @@
export interface DelayProps {
closeDelay?: number | string;
openDelay?: number | string;
}
export declare const makeDelayProps: <Defaults extends {
closeDelay?: unknown;
openDelay?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
closeDelay: unknown extends Defaults["closeDelay"] ? (NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["closeDelay"] ? string | number : string | number | Defaults["closeDelay"]>;
default: unknown extends Defaults["closeDelay"] ? string | number : Defaults["closeDelay"] | NonNullable<string | number>;
};
openDelay: unknown extends Defaults["openDelay"] ? (NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["openDelay"] ? string | number : string | number | Defaults["openDelay"]>;
default: unknown extends Defaults["openDelay"] ? string | number : Defaults["openDelay"] | NonNullable<string | number>;
};
};
export declare function useDelay(props: DelayProps, cb?: (value: boolean) => void): {
clearDelay: () => void;
runOpenDelay: () => Promise<unknown>;
runCloseDelay: (options?: {
minDelay: number;
}) => Promise<unknown>;
};
+33
View File
@@ -0,0 +1,33 @@
// Utilities
import { defer, propsFactory } from "../util/index.js"; // Types
// Composables
export const makeDelayProps = propsFactory({
closeDelay: [Number, String],
openDelay: [Number, String]
}, 'delay');
export function useDelay(props, cb) {
let clearDelay = () => {};
function runDelay(isOpening, options) {
clearDelay?.();
const delay = isOpening ? props.openDelay : props.closeDelay;
const normalizedDelay = Math.max(options?.minDelay ?? 0, Number(delay ?? 0));
return new Promise(resolve => {
clearDelay = defer(normalizedDelay, () => {
cb?.(isOpening);
resolve(isOpening);
});
});
}
function runOpenDelay() {
return runDelay(true);
}
function runCloseDelay(options) {
return runDelay(false, options);
}
return {
clearDelay,
runOpenDelay,
runCloseDelay
};
}
//# sourceMappingURL=delay.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"delay.js","names":["defer","propsFactory","makeDelayProps","closeDelay","Number","String","openDelay","useDelay","props","cb","clearDelay","runDelay","isOpening","options","delay","normalizedDelay","Math","max","minDelay","Promise","resolve","runOpenDelay","runCloseDelay"],"sources":["../../src/composables/delay.ts"],"sourcesContent":["// Utilities\nimport { defer, propsFactory } from '@/util'\n\n// Types\nexport interface DelayProps {\n closeDelay?: number | string\n openDelay?: number | string\n}\n\n// Composables\nexport const makeDelayProps = propsFactory({\n closeDelay: [Number, String],\n openDelay: [Number, String],\n}, 'delay')\n\nexport function useDelay (props: DelayProps, cb?: (value: boolean) => void) {\n let clearDelay: (() => void) = () => {}\n\n function runDelay (isOpening: boolean, options?: { minDelay: number }) {\n clearDelay?.()\n\n const delay = isOpening ? props.openDelay : props.closeDelay\n\n const normalizedDelay = Math.max(\n options?.minDelay ?? 0,\n Number(delay ?? 0)\n )\n\n return new Promise(resolve => {\n clearDelay = defer(normalizedDelay, () => {\n cb?.(isOpening)\n resolve(isOpening)\n })\n })\n }\n\n function runOpenDelay () {\n return runDelay(true)\n }\n\n function runCloseDelay (options?: { minDelay: number }) {\n return runDelay(false, options)\n }\n\n return {\n clearDelay,\n runOpenDelay,\n runCloseDelay,\n }\n}\n"],"mappings":"AAAA;AAAA,SACSA,KAAK,EAAEC,YAAY,4BAE5B;AAMA;AACA,OAAO,MAAMC,cAAc,GAAGD,YAAY,CAAC;EACzCE,UAAU,EAAE,CAACC,MAAM,EAAEC,MAAM,CAAC;EAC5BC,SAAS,EAAE,CAACF,MAAM,EAAEC,MAAM;AAC5B,CAAC,EAAE,OAAO,CAAC;AAEX,OAAO,SAASE,QAAQA,CAAEC,KAAiB,EAAEC,EAA6B,EAAE;EAC1E,IAAIC,UAAwB,GAAGA,CAAA,KAAM,CAAC,CAAC;EAEvC,SAASC,QAAQA,CAAEC,SAAkB,EAAEC,OAA8B,EAAE;IACrEH,UAAU,GAAG,CAAC;IAEd,MAAMI,KAAK,GAAGF,SAAS,GAAGJ,KAAK,CAACF,SAAS,GAAGE,KAAK,CAACL,UAAU;IAE5D,MAAMY,eAAe,GAAGC,IAAI,CAACC,GAAG,CAC9BJ,OAAO,EAAEK,QAAQ,IAAI,CAAC,EACtBd,MAAM,CAACU,KAAK,IAAI,CAAC,CACnB,CAAC;IAED,OAAO,IAAIK,OAAO,CAACC,OAAO,IAAI;MAC5BV,UAAU,GAAGV,KAAK,CAACe,eAAe,EAAE,MAAM;QACxCN,EAAE,GAAGG,SAAS,CAAC;QACfQ,OAAO,CAACR,SAAS,CAAC;MACpB,CAAC,CAAC;IACJ,CAAC,CAAC;EACJ;EAEA,SAASS,YAAYA,CAAA,EAAI;IACvB,OAAOV,QAAQ,CAAC,IAAI,CAAC;EACvB;EAEA,SAASW,aAAaA,CAAET,OAA8B,EAAE;IACtD,OAAOF,QAAQ,CAAC,KAAK,EAAEE,OAAO,CAAC;EACjC;EAEA,OAAO;IACLH,UAAU;IACVW,YAAY;IACZC;EACF,CAAC;AACH","ignoreList":[]}
+24
View File
@@ -0,0 +1,24 @@
import type { PropType } from 'vue';
export type Density = null | 'default' | 'comfortable' | 'compact';
export interface DensityProps {
density?: Density;
}
export declare const makeDensityProps: <Defaults extends {
density?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
density: unknown extends Defaults["density"] ? {
type: PropType<Density>;
default: string;
validator: (v: any) => boolean;
} : Omit<{
type: PropType<Density>;
default: string;
validator: (v: any) => boolean;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["density"] ? Density : Defaults["density"] | Density>;
default: unknown extends Defaults["density"] ? Density : Defaults["density"] | NonNullable<Density>;
};
};
export declare function useDensity(props: DensityProps, name?: string): {
densityClasses: Readonly<import("vue").Ref<string, string>>;
};
+25
View File
@@ -0,0 +1,25 @@
// Utilities
import { toRef } from 'vue';
import { getCurrentInstanceName, propsFactory } from "../util/index.js"; // Types
const allowedDensities = [null, 'default', 'comfortable', 'compact'];
// typeof allowedDensities[number] evaluates to any
// when generating api types for whatever reason.
// Composables
export const makeDensityProps = propsFactory({
density: {
type: String,
default: 'default',
validator: v => allowedDensities.includes(v)
}
}, 'density');
export function useDensity(props, name = getCurrentInstanceName()) {
const densityClasses = toRef(() => {
return `${name}--density-${props.density}`;
});
return {
densityClasses
};
}
//# sourceMappingURL=density.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"density.js","names":["toRef","getCurrentInstanceName","propsFactory","allowedDensities","makeDensityProps","density","type","String","default","validator","v","includes","useDensity","props","name","densityClasses"],"sources":["../../src/composables/density.ts"],"sourcesContent":["// Utilities\nimport { toRef } from 'vue'\nimport { getCurrentInstanceName, propsFactory } from '@/util'\n\n// Types\nimport type { PropType } from 'vue'\n\nconst allowedDensities = [null, 'default', 'comfortable', 'compact'] as const\n\n// typeof allowedDensities[number] evaluates to any\n// when generating api types for whatever reason.\nexport type Density = null | 'default' | 'comfortable' | 'compact'\n\nexport interface DensityProps {\n density?: Density\n}\n\n// Composables\nexport const makeDensityProps = propsFactory({\n density: {\n type: String as PropType<Density>,\n default: 'default',\n validator: (v: any) => allowedDensities.includes(v),\n },\n}, 'density')\n\nexport function useDensity (\n props: DensityProps,\n name = getCurrentInstanceName(),\n) {\n const densityClasses = toRef(() => {\n return `${name}--density-${props.density}`\n })\n\n return { densityClasses }\n}\n"],"mappings":"AAAA;AACA,SAASA,KAAK,QAAQ,KAAK;AAAA,SAClBC,sBAAsB,EAAEC,YAAY,4BAE7C;AAGA,MAAMC,gBAAgB,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,CAAU;;AAE7E;AACA;;AAOA;AACA,OAAO,MAAMC,gBAAgB,GAAGF,YAAY,CAAC;EAC3CG,OAAO,EAAE;IACPC,IAAI,EAAEC,MAA2B;IACjCC,OAAO,EAAE,SAAS;IAClBC,SAAS,EAAGC,CAAM,IAAKP,gBAAgB,CAACQ,QAAQ,CAACD,CAAC;EACpD;AACF,CAAC,EAAE,SAAS,CAAC;AAEb,OAAO,SAASE,UAAUA,CACxBC,KAAmB,EACnBC,IAAI,GAAGb,sBAAsB,CAAC,CAAC,EAC/B;EACA,MAAMc,cAAc,GAAGf,KAAK,CAAC,MAAM;IACjC,OAAO,GAAGc,IAAI,aAAaD,KAAK,CAACR,OAAO,EAAE;EAC5C,CAAC,CAAC;EAEF,OAAO;IAAEU;EAAe,CAAC;AAC3B","ignoreList":[]}
+44
View File
@@ -0,0 +1,44 @@
export interface DimensionProps {
height?: number | string;
maxHeight?: number | string;
maxWidth?: number | string;
minHeight?: number | string;
minWidth?: number | string;
width?: number | string;
}
export declare const makeDimensionProps: <Defaults extends {
height?: unknown;
maxHeight?: unknown;
maxWidth?: unknown;
minHeight?: unknown;
minWidth?: unknown;
width?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
height: unknown extends Defaults["height"] ? (NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["height"] ? string | number : string | number | Defaults["height"]>;
default: unknown extends Defaults["height"] ? string | number : Defaults["height"] | NonNullable<string | number>;
};
maxHeight: unknown extends Defaults["maxHeight"] ? (NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["maxHeight"] ? string | number : string | number | Defaults["maxHeight"]>;
default: unknown extends Defaults["maxHeight"] ? string | number : Defaults["maxHeight"] | NonNullable<string | number>;
};
maxWidth: unknown extends Defaults["maxWidth"] ? (NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["maxWidth"] ? string | number : string | number | Defaults["maxWidth"]>;
default: unknown extends Defaults["maxWidth"] ? string | number : Defaults["maxWidth"] | NonNullable<string | number>;
};
minHeight: unknown extends Defaults["minHeight"] ? (NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["minHeight"] ? string | number : string | number | Defaults["minHeight"]>;
default: unknown extends Defaults["minHeight"] ? string | number : Defaults["minHeight"] | NonNullable<string | number>;
};
minWidth: unknown extends Defaults["minWidth"] ? (NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["minWidth"] ? string | number : string | number | Defaults["minWidth"]>;
default: unknown extends Defaults["minWidth"] ? string | number : Defaults["minWidth"] | NonNullable<string | number>;
};
width: unknown extends Defaults["width"] ? (NumberConstructor | StringConstructor)[] : {
type: import("vue").PropType<unknown extends Defaults["width"] ? string | number : string | number | Defaults["width"]>;
default: unknown extends Defaults["width"] ? string | number : Defaults["width"] | NonNullable<string | number>;
};
};
export declare function useDimension(props: DimensionProps): {
dimensionStyles: import("vue").ComputedRef<Record<string, any>>;
};
+34
View File
@@ -0,0 +1,34 @@
// Utilities
import { computed } from 'vue';
import { convertToUnit, propsFactory } from "../util/index.js"; // Types
// Composables
export const makeDimensionProps = propsFactory({
height: [Number, String],
maxHeight: [Number, String],
maxWidth: [Number, String],
minHeight: [Number, String],
minWidth: [Number, String],
width: [Number, String]
}, 'dimension');
export function useDimension(props) {
const dimensionStyles = computed(() => {
const styles = {};
const height = convertToUnit(props.height);
const maxHeight = convertToUnit(props.maxHeight);
const maxWidth = convertToUnit(props.maxWidth);
const minHeight = convertToUnit(props.minHeight);
const minWidth = convertToUnit(props.minWidth);
const width = convertToUnit(props.width);
if (height != null) styles.height = height;
if (maxHeight != null) styles.maxHeight = maxHeight;
if (maxWidth != null) styles.maxWidth = maxWidth;
if (minHeight != null) styles.minHeight = minHeight;
if (minWidth != null) styles.minWidth = minWidth;
if (width != null) styles.width = width;
return styles;
});
return {
dimensionStyles
};
}
//# sourceMappingURL=dimensions.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"dimensions.js","names":["computed","convertToUnit","propsFactory","makeDimensionProps","height","Number","String","maxHeight","maxWidth","minHeight","minWidth","width","useDimension","props","dimensionStyles","styles"],"sources":["../../src/composables/dimensions.ts"],"sourcesContent":["// Utilities\nimport { computed } from 'vue'\nimport { convertToUnit, propsFactory } from '@/util'\n\n// Types\nexport interface DimensionProps {\n height?: number | string\n maxHeight?: number | string\n maxWidth?: number | string\n minHeight?: number | string\n minWidth?: number | string\n width?: number | string\n}\n\n// Composables\nexport const makeDimensionProps = propsFactory({\n height: [Number, String],\n maxHeight: [Number, String],\n maxWidth: [Number, String],\n minHeight: [Number, String],\n minWidth: [Number, String],\n width: [Number, String],\n}, 'dimension')\n\nexport function useDimension (props: DimensionProps) {\n const dimensionStyles = computed(() => {\n const styles: Record<string, any> = {}\n\n const height = convertToUnit(props.height)\n const maxHeight = convertToUnit(props.maxHeight)\n const maxWidth = convertToUnit(props.maxWidth)\n const minHeight = convertToUnit(props.minHeight)\n const minWidth = convertToUnit(props.minWidth)\n const width = convertToUnit(props.width)\n\n if (height != null) styles.height = height\n if (maxHeight != null) styles.maxHeight = maxHeight\n if (maxWidth != null) styles.maxWidth = maxWidth\n if (minHeight != null) styles.minHeight = minHeight\n if (minWidth != null) styles.minWidth = minWidth\n if (width != null) styles.width = width\n\n return styles\n })\n\n return { dimensionStyles }\n}\n"],"mappings":"AAAA;AACA,SAASA,QAAQ,QAAQ,KAAK;AAAA,SACrBC,aAAa,EAAEC,YAAY,4BAEpC;AAUA;AACA,OAAO,MAAMC,kBAAkB,GAAGD,YAAY,CAAC;EAC7CE,MAAM,EAAE,CAACC,MAAM,EAAEC,MAAM,CAAC;EACxBC,SAAS,EAAE,CAACF,MAAM,EAAEC,MAAM,CAAC;EAC3BE,QAAQ,EAAE,CAACH,MAAM,EAAEC,MAAM,CAAC;EAC1BG,SAAS,EAAE,CAACJ,MAAM,EAAEC,MAAM,CAAC;EAC3BI,QAAQ,EAAE,CAACL,MAAM,EAAEC,MAAM,CAAC;EAC1BK,KAAK,EAAE,CAACN,MAAM,EAAEC,MAAM;AACxB,CAAC,EAAE,WAAW,CAAC;AAEf,OAAO,SAASM,YAAYA,CAAEC,KAAqB,EAAE;EACnD,MAAMC,eAAe,GAAGd,QAAQ,CAAC,MAAM;IACrC,MAAMe,MAA2B,GAAG,CAAC,CAAC;IAEtC,MAAMX,MAAM,GAAGH,aAAa,CAACY,KAAK,CAACT,MAAM,CAAC;IAC1C,MAAMG,SAAS,GAAGN,aAAa,CAACY,KAAK,CAACN,SAAS,CAAC;IAChD,MAAMC,QAAQ,GAAGP,aAAa,CAACY,KAAK,CAACL,QAAQ,CAAC;IAC9C,MAAMC,SAAS,GAAGR,aAAa,CAACY,KAAK,CAACJ,SAAS,CAAC;IAChD,MAAMC,QAAQ,GAAGT,aAAa,CAACY,KAAK,CAACH,QAAQ,CAAC;IAC9C,MAAMC,KAAK,GAAGV,aAAa,CAACY,KAAK,CAACF,KAAK,CAAC;IAExC,IAAIP,MAAM,IAAI,IAAI,EAAEW,MAAM,CAACX,MAAM,GAAGA,MAAM;IAC1C,IAAIG,SAAS,IAAI,IAAI,EAAEQ,MAAM,CAACR,SAAS,GAAGA,SAAS;IACnD,IAAIC,QAAQ,IAAI,IAAI,EAAEO,MAAM,CAACP,QAAQ,GAAGA,QAAQ;IAChD,IAAIC,SAAS,IAAI,IAAI,EAAEM,MAAM,CAACN,SAAS,GAAGA,SAAS;IACnD,IAAIC,QAAQ,IAAI,IAAI,EAAEK,MAAM,CAACL,QAAQ,GAAGA,QAAQ;IAChD,IAAIC,KAAK,IAAI,IAAI,EAAEI,MAAM,CAACJ,KAAK,GAAGA,KAAK;IAEvC,OAAOI,MAAM;EACf,CAAC,CAAC;EAEF,OAAO;IAAED;EAAgB,CAAC;AAC5B","ignoreList":[]}
+18
View File
@@ -0,0 +1,18 @@
import type { Component, DirectiveBinding, ObjectDirective, VNode } from 'vue';
import type { ComponentInstance } from '../util/index.js';
type ExcludeProps = 'v-slots' | `v-slot:${string}` | `on${Uppercase<string>}${string}` | 'key' | 'ref' | 'ref_for' | 'ref_key' | '$children';
declare const CustomDirectiveSymbol: unique symbol;
type DirectiveHook<B extends DirectiveBinding> = (el: any, binding: B, vnode: VNode<any, any>, prevVNode: VNode<any, any>) => void;
export interface CustomDirective<B extends DirectiveBinding = DirectiveBinding> {
created?: DirectiveHook<B>;
beforeMount?: DirectiveHook<B>;
mounted?: DirectiveHook<B>;
beforeUpdate?: DirectiveHook<B>;
updated?: DirectiveHook<B>;
beforeUnmount?: DirectiveHook<B>;
unmounted?: DirectiveHook<B>;
[CustomDirectiveSymbol]: true;
}
export declare function useDirectiveComponent<Binding extends DirectiveBinding>(component: string | Component, props?: (binding: Binding) => Record<string, any>): CustomDirective<Binding>;
export declare function useDirectiveComponent<C extends Component, Props = Omit<ComponentInstance<C>['$props'], ExcludeProps>>(component: string | C, props?: Record<string, any>): ObjectDirective<any, Props>;
+73
View File
@@ -0,0 +1,73 @@
// Utilities
import { h, mergeProps, render, resolveComponent } from 'vue';
import { consoleError, isObject } from "../util/index.js"; // Types
export function useDirectiveComponent(component, props) {
const concreteComponent = typeof component === 'string' ? resolveComponent(component) : component;
const hook = mountComponent(concreteComponent, props);
return {
mounted: hook,
updated: hook,
unmounted(el) {
render(null, el);
}
};
}
function mountComponent(component, props) {
return function (el, binding, vnode) {
const _props = typeof props === 'function' ? props(binding) : props;
const text = binding.value?.text ?? binding.value ?? _props?.text;
const value = isObject(binding.value) ? binding.value : {};
// Get the children from the props or directive value, or the element's children
const children = () => text ?? el.textContent;
// If vnode.ctx is the same as the instance, then we're bound to a plain element
// and need to find the nearest parent component instance to inherit provides from
const provides = (vnode.ctx === binding.instance.$ ? findComponentParent(vnode, binding.instance.$)?.provides : vnode.ctx?.provides) ?? binding.instance.$.provides;
const node = h(component, mergeProps(_props, value), children);
node.appContext = Object.assign(Object.create(null), binding.instance.$.appContext, {
provides
});
render(node, el);
};
}
function findComponentParent(vnode, root) {
// Walk the tree from root until we find the child vnode
const stack = new Set();
const walk = children => {
for (const child of children) {
if (!child) continue;
if (child === vnode || child.el && vnode.el && child.el === vnode.el) {
return true;
}
stack.add(child);
let result;
if (child.suspense) {
result = walk([child.ssContent]);
} else if (Array.isArray(child.children)) {
result = walk(child.children);
} else if (child.component?.vnode) {
result = walk([child.component?.subTree]);
}
if (result) {
return result;
}
stack.delete(child);
}
return false;
};
if (!walk([root.subTree])) {
consoleError('Could not find original vnode, component will not inherit provides');
return root;
}
// Return the first component parent
const result = Array.from(stack).reverse();
for (const child of result) {
if (child.component) {
return child.component;
}
}
return root;
}
//# sourceMappingURL=directiveComponent.js.map
File diff suppressed because one or more lines are too long
+114
View File
@@ -0,0 +1,114 @@
import type { InjectionKey, PropType, Ref } from 'vue';
export declare const breakpoints: readonly ['sm', 'md', 'lg', 'xl', 'xxl'];
export type Breakpoint = (typeof breakpoints)[number];
export type DisplayBreakpoint = 'xs' | Breakpoint;
export type DisplayThresholds = {
[key in DisplayBreakpoint]: number;
};
export interface DisplayProps {
mobile?: boolean | null;
mobileBreakpoint?: number | DisplayBreakpoint;
}
export interface DisplayOptions {
mobileBreakpoint?: number | DisplayBreakpoint;
thresholds?: Partial<DisplayThresholds>;
}
export interface InternalDisplayOptions {
mobileBreakpoint: number | DisplayBreakpoint;
thresholds: DisplayThresholds;
}
export type SSROptions = boolean | {
clientWidth: number;
clientHeight?: number;
};
export interface DisplayPlatform {
android: boolean;
ios: boolean;
cordova: boolean;
electron: boolean;
chrome: boolean;
edge: boolean;
firefox: boolean;
opera: boolean;
win: boolean;
mac: boolean;
linux: boolean;
touch: boolean;
ssr: boolean;
}
export interface DisplayInstance {
xs: Ref<boolean>;
sm: Ref<boolean>;
md: Ref<boolean>;
lg: Ref<boolean>;
xl: Ref<boolean>;
xxl: Ref<boolean>;
smAndUp: Ref<boolean>;
mdAndUp: Ref<boolean>;
lgAndUp: Ref<boolean>;
xlAndUp: Ref<boolean>;
smAndDown: Ref<boolean>;
mdAndDown: Ref<boolean>;
lgAndDown: Ref<boolean>;
xlAndDown: Ref<boolean>;
name: Ref<DisplayBreakpoint>;
height: Ref<number>;
width: Ref<number>;
mobile: Ref<boolean>;
mobileBreakpoint: Ref<number | DisplayBreakpoint>;
platform: Ref<DisplayPlatform>;
thresholds: Ref<DisplayThresholds>;
update(): void;
}
export declare const DisplaySymbol: InjectionKey<DisplayInstance>;
export declare function createDisplay(options?: DisplayOptions, ssr?: SSROptions): DisplayInstance;
export declare const makeDisplayProps: <Defaults extends {
mobile?: unknown;
mobileBreakpoint?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
mobile: unknown extends Defaults["mobile"] ? {
type: PropType<boolean | null>;
default: boolean;
} : Omit<{
type: PropType<boolean | null>;
default: boolean;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["mobile"] ? boolean | null : boolean | Defaults["mobile"] | null>;
default: unknown extends Defaults["mobile"] ? boolean | null : Defaults["mobile"] | NonNullable<boolean | null>;
};
mobileBreakpoint: unknown extends Defaults["mobileBreakpoint"] ? PropType<number | DisplayBreakpoint> : {
type: PropType<unknown extends Defaults["mobileBreakpoint"] ? number | DisplayBreakpoint : number | Defaults["mobileBreakpoint"] | DisplayBreakpoint>;
default: unknown extends Defaults["mobileBreakpoint"] ? number | DisplayBreakpoint : Defaults["mobileBreakpoint"] | NonNullable<number | DisplayBreakpoint>;
};
};
export declare function useDisplay(props?: DisplayProps, name?: string): {
xs: Ref<boolean>;
sm: Ref<boolean>;
md: Ref<boolean>;
lg: Ref<boolean>;
xl: Ref<boolean>;
xxl: Ref<boolean>;
smAndUp: Ref<boolean>;
mdAndUp: Ref<boolean>;
lgAndUp: Ref<boolean>;
xlAndUp: Ref<boolean>;
smAndDown: Ref<boolean>;
mdAndDown: Ref<boolean>;
lgAndDown: Ref<boolean>;
xlAndDown: Ref<boolean>;
name: Ref<DisplayBreakpoint>;
height: Ref<number>;
width: Ref<number>;
mobileBreakpoint: Ref<number | DisplayBreakpoint>;
platform: Ref<DisplayPlatform>;
thresholds: Ref<DisplayThresholds>;
/** @internal */
ssr: boolean;
update(): void;
displayClasses: Readonly<Ref<{
[x: string]: boolean;
}, {
[x: string]: boolean;
}>>;
mobile: import("vue").ComputedRef<boolean>;
};
+162
View File
@@ -0,0 +1,162 @@
// Utilities
import { computed, inject, onScopeDispose, reactive, shallowRef, toRef, toRefs, watchEffect } from 'vue';
import { getCurrentInstanceName, mergeDeep, propsFactory } from "../util/index.js";
import { IN_BROWSER, SUPPORTS_TOUCH } from "../util/globals.js"; // Types
export const breakpoints = ['sm', 'md', 'lg', 'xl', 'xxl']; // no xs
export const DisplaySymbol = Symbol.for('vuetify:display');
const defaultDisplayOptions = {
mobileBreakpoint: 'lg',
thresholds: {
xs: 0,
sm: 600,
md: 840,
lg: 1145,
xl: 1545,
xxl: 2138
}
};
const parseDisplayOptions = (options = defaultDisplayOptions) => {
return mergeDeep(defaultDisplayOptions, options);
};
function getClientWidth(ssr) {
return IN_BROWSER && !ssr ? window.innerWidth : typeof ssr === 'object' && ssr.clientWidth || 0;
}
function getClientHeight(ssr) {
return IN_BROWSER && !ssr ? window.innerHeight : typeof ssr === 'object' && ssr.clientHeight || 0;
}
function getPlatform(ssr) {
const userAgent = IN_BROWSER && !ssr ? window.navigator.userAgent : 'ssr';
function match(regexp) {
return Boolean(userAgent.match(regexp));
}
const android = match(/android/i);
const ios = match(/iphone|ipad|ipod/i);
const cordova = match(/cordova/i);
const electron = match(/electron/i);
const chrome = match(/chrome/i);
const edge = match(/edge/i);
const firefox = match(/firefox/i);
const opera = match(/opera/i);
const win = match(/win/i);
const mac = match(/mac/i);
const linux = match(/linux/i);
return {
android,
ios,
cordova,
electron,
chrome,
edge,
firefox,
opera,
win,
mac,
linux,
touch: SUPPORTS_TOUCH,
ssr: userAgent === 'ssr'
};
}
export function createDisplay(options, ssr) {
const {
thresholds,
mobileBreakpoint
} = parseDisplayOptions(options);
const height = shallowRef(getClientHeight(ssr));
const platform = shallowRef(getPlatform(ssr));
const state = reactive({});
const width = shallowRef(getClientWidth(ssr));
function updateSize() {
height.value = getClientHeight();
width.value = getClientWidth();
}
function update() {
updateSize();
platform.value = getPlatform();
}
// eslint-disable-next-line max-statements
watchEffect(() => {
const xs = width.value < thresholds.sm;
const sm = width.value < thresholds.md && !xs;
const md = width.value < thresholds.lg && !(sm || xs);
const lg = width.value < thresholds.xl && !(md || sm || xs);
const xl = width.value < thresholds.xxl && !(lg || md || sm || xs);
const xxl = width.value >= thresholds.xxl;
const name = xs ? 'xs' : sm ? 'sm' : md ? 'md' : lg ? 'lg' : xl ? 'xl' : 'xxl';
const breakpointValue = typeof mobileBreakpoint === 'number' ? mobileBreakpoint : thresholds[mobileBreakpoint];
const mobile = width.value < breakpointValue;
state.xs = xs;
state.sm = sm;
state.md = md;
state.lg = lg;
state.xl = xl;
state.xxl = xxl;
state.smAndUp = !xs;
state.mdAndUp = !(xs || sm);
state.lgAndUp = !(xs || sm || md);
state.xlAndUp = !(xs || sm || md || lg);
state.smAndDown = !(md || lg || xl || xxl);
state.mdAndDown = !(lg || xl || xxl);
state.lgAndDown = !(xl || xxl);
state.xlAndDown = !xxl;
state.name = name;
state.height = height.value;
state.width = width.value;
state.mobile = mobile;
state.mobileBreakpoint = mobileBreakpoint;
state.platform = platform.value;
state.thresholds = thresholds;
});
if (IN_BROWSER) {
window.addEventListener('resize', updateSize, {
passive: true
});
onScopeDispose(() => {
window.removeEventListener('resize', updateSize);
}, true);
}
return {
...toRefs(state),
update,
ssr: !!ssr
};
}
export const makeDisplayProps = propsFactory({
mobile: {
type: Boolean,
default: false
},
mobileBreakpoint: [Number, String]
}, 'display');
export function useDisplay(props = {
mobile: null
}, name = getCurrentInstanceName()) {
const display = inject(DisplaySymbol);
if (!display) throw new Error('Could not find Vuetify display injection');
const mobile = computed(() => {
if (props.mobile) {
return true;
} else if (typeof props.mobileBreakpoint === 'number') {
return display.width.value < props.mobileBreakpoint;
} else if (props.mobileBreakpoint) {
return display.width.value < display.thresholds.value[props.mobileBreakpoint];
} else if (props.mobile === null) {
return display.mobile.value;
} else {
return false;
}
});
const displayClasses = toRef(() => {
if (!name) return {};
return {
[`${name}--mobile`]: mobile.value
};
});
return {
...display,
displayClasses,
mobile
};
}
//# sourceMappingURL=display.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
export declare function useDocumentVisibility(): import("vue").ShallowRef<DocumentVisibilityState, DocumentVisibilityState>;
+19
View File
@@ -0,0 +1,19 @@
// Utilities
import { onBeforeUnmount, shallowRef } from 'vue';
import { IN_BROWSER } from "../util/globals.js";
export function useDocumentVisibility() {
const visibility = shallowRef(IN_BROWSER ? document.visibilityState : 'visible');
if (IN_BROWSER) {
const onVisibilityChange = () => {
visibility.value = document.visibilityState;
};
document.addEventListener('visibilitychange', onVisibilityChange, {
passive: true
});
onBeforeUnmount(() => {
document.removeEventListener('visibilitychange', onVisibilityChange);
});
}
return visibility;
}
//# sourceMappingURL=documentVisibility.js.map
@@ -0,0 +1 @@
{"version":3,"file":"documentVisibility.js","names":["onBeforeUnmount","shallowRef","IN_BROWSER","useDocumentVisibility","visibility","document","visibilityState","onVisibilityChange","value","addEventListener","passive","removeEventListener"],"sources":["../../src/composables/documentVisibility.ts"],"sourcesContent":["// Utilities\nimport { onBeforeUnmount, shallowRef } from 'vue'\nimport { IN_BROWSER } from '@/util/globals'\n\nexport function useDocumentVisibility () {\n const visibility = shallowRef(IN_BROWSER ? document.visibilityState : 'visible')\n\n if (IN_BROWSER) {\n const onVisibilityChange = () => {\n visibility.value = document.visibilityState\n }\n document.addEventListener('visibilitychange', onVisibilityChange, { passive: true })\n onBeforeUnmount(() => {\n document.removeEventListener('visibilitychange', onVisibilityChange)\n })\n }\n\n return visibility\n}\n"],"mappings":"AAAA;AACA,SAASA,eAAe,EAAEC,UAAU,QAAQ,KAAK;AAAA,SACxCC,UAAU;AAEnB,OAAO,SAASC,qBAAqBA,CAAA,EAAI;EACvC,MAAMC,UAAU,GAAGH,UAAU,CAACC,UAAU,GAAGG,QAAQ,CAACC,eAAe,GAAG,SAAS,CAAC;EAEhF,IAAIJ,UAAU,EAAE;IACd,MAAMK,kBAAkB,GAAGA,CAAA,KAAM;MAC/BH,UAAU,CAACI,KAAK,GAAGH,QAAQ,CAACC,eAAe;IAC7C,CAAC;IACDD,QAAQ,CAACI,gBAAgB,CAAC,kBAAkB,EAAEF,kBAAkB,EAAE;MAAEG,OAAO,EAAE;IAAK,CAAC,CAAC;IACpFV,eAAe,CAAC,MAAM;MACpBK,QAAQ,CAACM,mBAAmB,CAAC,kBAAkB,EAAEJ,kBAAkB,CAAC;IACtE,CAAC,CAAC;EACJ;EAEA,OAAOH,UAAU;AACnB","ignoreList":[]}
+23
View File
@@ -0,0 +1,23 @@
import type { Ref } from 'vue';
export interface ElevationProps {
elevation?: number | string | null;
}
export declare const makeElevationProps: <Defaults extends {
elevation?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
elevation: unknown extends Defaults["elevation"] ? {
type: (NumberConstructor | StringConstructor)[];
validator: (value: string | number) => boolean;
} : Omit<{
type: (NumberConstructor | StringConstructor)[];
validator: (value: string | number) => boolean;
}, "default" | "type"> & {
type: import("vue").PropType<unknown extends Defaults["elevation"] ? string | number : string | number | Defaults["elevation"]>;
default: unknown extends Defaults["elevation"] ? string | number : Defaults["elevation"] | NonNullable<string | number>;
};
};
type ElevationData = {
elevationClasses: Ref<string[]>;
};
export declare function useElevation(props: ElevationProps | Ref<number | string | undefined>): ElevationData;
+22
View File
@@ -0,0 +1,22 @@
// Utilities
import { isRef, toRef } from 'vue';
import { propsFactory } from "../util/index.js"; // Types
// Composables
export const makeElevationProps = propsFactory({
elevation: {
type: [Number, String],
// no limit to allow both 0-6 (MD3) and legacy 0-24 (MD2)
validator: value => parseInt(value) >= 0
}
}, 'elevation');
export function useElevation(props) {
const elevationClasses = toRef(() => {
const elevation = isRef(props) ? props.value : props.elevation;
if (elevation == null) return [];
return [`elevation-${parseInt(elevation)}`];
});
return {
elevationClasses
};
}
//# sourceMappingURL=elevation.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"elevation.js","names":["isRef","toRef","propsFactory","makeElevationProps","elevation","type","Number","String","validator","value","parseInt","useElevation","props","elevationClasses"],"sources":["../../src/composables/elevation.ts"],"sourcesContent":["// Utilities\nimport { isRef, toRef } from 'vue'\nimport { propsFactory } from '@/util'\n\n// Types\nimport type { Ref } from 'vue'\nexport interface ElevationProps {\n elevation?: number | string | null\n}\n\n// Composables\nexport const makeElevationProps = propsFactory({\n elevation: {\n type: [Number, String],\n // no limit to allow both 0-6 (MD3) and legacy 0-24 (MD2)\n validator: (value: string | number) => parseInt(value) >= 0,\n },\n}, 'elevation')\n\ntype ElevationData = {\n elevationClasses: Ref<string[]>\n}\n\nexport function useElevation (props: ElevationProps | Ref<number | string | undefined>): ElevationData {\n const elevationClasses = toRef(() => {\n const elevation = isRef(props) ? props.value : props.elevation\n if (elevation == null) return []\n return [`elevation-${parseInt(elevation)}`]\n })\n\n return { elevationClasses }\n}\n"],"mappings":"AAAA;AACA,SAASA,KAAK,EAAEC,KAAK,QAAQ,KAAK;AAAA,SACzBC,YAAY,4BAErB;AAMA;AACA,OAAO,MAAMC,kBAAkB,GAAGD,YAAY,CAAC;EAC7CE,SAAS,EAAE;IACTC,IAAI,EAAE,CAACC,MAAM,EAAEC,MAAM,CAAC;IACtB;IACAC,SAAS,EAAGC,KAAsB,IAAKC,QAAQ,CAACD,KAAK,CAAC,IAAI;EAC5D;AACF,CAAC,EAAE,WAAW,CAAC;AAMf,OAAO,SAASE,YAAYA,CAAEC,KAAwD,EAAiB;EACrG,MAAMC,gBAAgB,GAAGZ,KAAK,CAAC,MAAM;IACnC,MAAMG,SAAS,GAAGJ,KAAK,CAACY,KAAK,CAAC,GAAGA,KAAK,CAACH,KAAK,GAAGG,KAAK,CAACR,SAAS;IAC9D,IAAIA,SAAS,IAAI,IAAI,EAAE,OAAO,EAAE;IAChC,OAAO,CAAC,aAAaM,QAAQ,CAACN,SAAS,CAAC,EAAE,CAAC;EAC7C,CAAC,CAAC;EAEF,OAAO;IAAES;EAAiB,CAAC;AAC7B","ignoreList":[]}
+4
View File
@@ -0,0 +1,4 @@
export declare function useFileDrop(): {
handleDrop: (e: DragEvent) => Promise<File[]>;
hasFilesOrFolders: (e: DragEvent) => boolean;
};
+49
View File
@@ -0,0 +1,49 @@
// Types
export function useFileDrop() {
function hasFilesOrFolders(e) {
const entries = [...(e.dataTransfer?.items ?? [])].filter(x => x.kind === 'file').map(x => x.webkitGetAsEntry()).filter(Boolean);
return entries.length > 0 || [...(e.dataTransfer?.files ?? [])].length > 0;
}
async function handleDrop(e) {
const result = [];
const entries = [...(e.dataTransfer?.items ?? [])].filter(x => x.kind === 'file').map(x => x.webkitGetAsEntry()).filter(Boolean);
if (entries.length) {
for (const entry of entries) {
const files = await traverseFileTree(entry, appendIfDirectory('.', entry));
result.push(...files.map(x => x.file));
}
} else {
result.push(...[...(e.dataTransfer?.files ?? [])]);
}
return result;
}
return {
handleDrop,
hasFilesOrFolders
};
}
function traverseFileTree(item, path = '') {
return new Promise((resolve, reject) => {
if (item.isFile) {
const fileEntry = item;
fileEntry.file(file => resolve([{
file,
path
}]), reject);
} else if (item.isDirectory) {
const directoryReader = item.createReader();
directoryReader.readEntries(async entries => {
const files = [];
for (const entry of entries) {
files.push(...(await traverseFileTree(entry, appendIfDirectory(path, entry))));
}
resolve(files);
});
}
});
}
function appendIfDirectory(path, item) {
return item.isDirectory ? `${path}/${item.name}` : path;
}
//# sourceMappingURL=fileDrop.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"fileDrop.js","names":["useFileDrop","hasFilesOrFolders","e","entries","dataTransfer","items","filter","x","kind","map","webkitGetAsEntry","Boolean","length","files","handleDrop","result","entry","traverseFileTree","appendIfDirectory","push","file","item","path","Promise","resolve","reject","isFile","fileEntry","isDirectory","directoryReader","createReader","readEntries","name"],"sources":["../../src/composables/fileDrop.ts"],"sourcesContent":["// Types\ntype FileSelection = { file: File, path: string }\n\nexport function useFileDrop () {\n function hasFilesOrFolders (e: DragEvent): boolean {\n const entries = [...e.dataTransfer?.items ?? []]\n .filter(x => x.kind === 'file')\n .map(x => x.webkitGetAsEntry())\n .filter(Boolean)\n\n return entries.length > 0 || [...e.dataTransfer?.files ?? []].length > 0\n }\n\n async function handleDrop (e: DragEvent) {\n const result: File[] = []\n\n const entries = [...e.dataTransfer?.items ?? []]\n .filter(x => x.kind === 'file')\n .map(x => x.webkitGetAsEntry())\n .filter(Boolean)\n\n if (entries.length) {\n for (const entry of entries) {\n const files = await traverseFileTree(entry!, appendIfDirectory('.', entry!))\n result.push(...files.map(x => x.file))\n }\n } else {\n result.push(...[...e.dataTransfer?.files ?? []])\n }\n\n return result\n }\n\n return {\n handleDrop,\n hasFilesOrFolders,\n }\n}\n\nfunction traverseFileTree (item: FileSystemEntry, path = ''): Promise<FileSelection[]> {\n return new Promise<FileSelection[]>((resolve, reject) => {\n if (item.isFile) {\n const fileEntry = item as FileSystemFileEntry\n fileEntry.file((file: File) => resolve([{ file, path }]), reject)\n } else if (item.isDirectory) {\n const directoryReader = (item as FileSystemDirectoryEntry).createReader()\n directoryReader.readEntries(async entries => {\n const files = [] as FileSelection[]\n for (const entry of entries) {\n files.push(...(await traverseFileTree(entry, appendIfDirectory(path, entry))))\n }\n resolve(files)\n })\n }\n })\n}\n\nfunction appendIfDirectory (path: string, item: FileSystemEntry) {\n return item.isDirectory\n ? `${path}/${item.name}`\n : path\n}\n"],"mappings":"AAAA;;AAGA,OAAO,SAASA,WAAWA,CAAA,EAAI;EAC7B,SAASC,iBAAiBA,CAAEC,CAAY,EAAW;IACjD,MAAMC,OAAO,GAAG,CAAC,IAAGD,CAAC,CAACE,YAAY,EAAEC,KAAK,IAAI,EAAE,EAAC,CAC7CC,MAAM,CAACC,CAAC,IAAIA,CAAC,CAACC,IAAI,KAAK,MAAM,CAAC,CAC9BC,GAAG,CAACF,CAAC,IAAIA,CAAC,CAACG,gBAAgB,CAAC,CAAC,CAAC,CAC9BJ,MAAM,CAACK,OAAO,CAAC;IAElB,OAAOR,OAAO,CAACS,MAAM,GAAG,CAAC,IAAI,CAAC,IAAGV,CAAC,CAACE,YAAY,EAAES,KAAK,IAAI,EAAE,EAAC,CAACD,MAAM,GAAG,CAAC;EAC1E;EAEA,eAAeE,UAAUA,CAAEZ,CAAY,EAAE;IACvC,MAAMa,MAAc,GAAG,EAAE;IAEzB,MAAMZ,OAAO,GAAG,CAAC,IAAGD,CAAC,CAACE,YAAY,EAAEC,KAAK,IAAI,EAAE,EAAC,CAC7CC,MAAM,CAACC,CAAC,IAAIA,CAAC,CAACC,IAAI,KAAK,MAAM,CAAC,CAC9BC,GAAG,CAACF,CAAC,IAAIA,CAAC,CAACG,gBAAgB,CAAC,CAAC,CAAC,CAC9BJ,MAAM,CAACK,OAAO,CAAC;IAElB,IAAIR,OAAO,CAACS,MAAM,EAAE;MAClB,KAAK,MAAMI,KAAK,IAAIb,OAAO,EAAE;QAC3B,MAAMU,KAAK,GAAG,MAAMI,gBAAgB,CAACD,KAAK,EAAGE,iBAAiB,CAAC,GAAG,EAAEF,KAAM,CAAC,CAAC;QAC5ED,MAAM,CAACI,IAAI,CAAC,GAAGN,KAAK,CAACJ,GAAG,CAACF,CAAC,IAAIA,CAAC,CAACa,IAAI,CAAC,CAAC;MACxC;IACF,CAAC,MAAM;MACLL,MAAM,CAACI,IAAI,CAAC,GAAG,CAAC,IAAGjB,CAAC,CAACE,YAAY,EAAES,KAAK,IAAI,EAAE,EAAC,CAAC;IAClD;IAEA,OAAOE,MAAM;EACf;EAEA,OAAO;IACLD,UAAU;IACVb;EACF,CAAC;AACH;AAEA,SAASgB,gBAAgBA,CAAEI,IAAqB,EAAEC,IAAI,GAAG,EAAE,EAA4B;EACrF,OAAO,IAAIC,OAAO,CAAkB,CAACC,OAAO,EAAEC,MAAM,KAAK;IACvD,IAAIJ,IAAI,CAACK,MAAM,EAAE;MACf,MAAMC,SAAS,GAAGN,IAA2B;MAC7CM,SAAS,CAACP,IAAI,CAAEA,IAAU,IAAKI,OAAO,CAAC,CAAC;QAAEJ,IAAI;QAAEE;MAAK,CAAC,CAAC,CAAC,EAAEG,MAAM,CAAC;IACnE,CAAC,MAAM,IAAIJ,IAAI,CAACO,WAAW,EAAE;MAC3B,MAAMC,eAAe,GAAIR,IAAI,CAA8BS,YAAY,CAAC,CAAC;MACzED,eAAe,CAACE,WAAW,CAAC,MAAM5B,OAAO,IAAI;QAC3C,MAAMU,KAAK,GAAG,EAAqB;QACnC,KAAK,MAAMG,KAAK,IAAIb,OAAO,EAAE;UAC3BU,KAAK,CAACM,IAAI,CAAC,IAAI,MAAMF,gBAAgB,CAACD,KAAK,EAAEE,iBAAiB,CAACI,IAAI,EAAEN,KAAK,CAAC,CAAC,CAAC,CAAC;QAChF;QACAQ,OAAO,CAACX,KAAK,CAAC;MAChB,CAAC,CAAC;IACJ;EACF,CAAC,CAAC;AACJ;AAEA,SAASK,iBAAiBA,CAAEI,IAAY,EAAED,IAAqB,EAAE;EAC/D,OAAOA,IAAI,CAACO,WAAW,GACnB,GAAGN,IAAI,IAAID,IAAI,CAACW,IAAI,EAAE,GACtBV,IAAI;AACV","ignoreList":[]}
+18
View File
@@ -0,0 +1,18 @@
export interface FileFilterProps {
filterByType?: string;
}
export type FileFilterResult = {
accepted: File[];
rejected: File[];
};
export declare const makeFileFilterProps: <Defaults extends {
filterByType?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
filterByType: unknown extends Defaults["filterByType"] ? StringConstructor : {
type: import("vue").PropType<unknown extends Defaults["filterByType"] ? string : string | Defaults["filterByType"]>;
default: unknown extends Defaults["filterByType"] ? string : string | Defaults["filterByType"];
};
};
export declare function useFileFilter(props: FileFilterProps): {
filterAccepted: (files: File[]) => FileFilterResult;
};
+38
View File
@@ -0,0 +1,38 @@
// Utilities
import { computed } from 'vue';
import { propsFactory } from "../util/index.js";
// Composables
export const makeFileFilterProps = propsFactory({
filterByType: String
}, 'file-accept');
export function useFileFilter(props) {
const fileFilter = computed(() => props.filterByType ? createFilter(props.filterByType) : null);
function filterAccepted(files) {
if (fileFilter.value) {
const accepted = files.filter(fileFilter.value);
return {
accepted,
rejected: files.filter(f => !accepted.includes(f))
};
}
return {
accepted: files,
rejected: []
};
}
return {
filterAccepted
};
}
function createFilter(v) {
const types = v.split(',').map(x => x.trim().toLowerCase());
const extensionsToMatch = types.filter(x => x.startsWith('.'));
const wildcards = types.filter(x => x.endsWith('/*'));
const typesToMatch = types.filter(x => !extensionsToMatch.includes(x) && !wildcards.includes(x));
return file => {
const extension = file.name.split('.').at(-1)?.toLowerCase() ?? '';
const typeGroup = file.type.split('/').at(0)?.toLowerCase() ?? '';
return typesToMatch.includes(file.type) || extensionsToMatch.includes(`.${extension}`) || wildcards.includes(`${typeGroup}/*`);
};
}
//# sourceMappingURL=fileFilter.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"fileFilter.js","names":["computed","propsFactory","makeFileFilterProps","filterByType","String","useFileFilter","props","fileFilter","createFilter","filterAccepted","files","value","accepted","filter","rejected","f","includes","v","types","split","map","x","trim","toLowerCase","extensionsToMatch","startsWith","wildcards","endsWith","typesToMatch","file","extension","name","at","typeGroup","type"],"sources":["../../src/composables/fileFilter.ts"],"sourcesContent":["// Utilities\nimport { computed } from 'vue'\nimport { propsFactory } from '@/util'\n\nexport interface FileFilterProps {\n filterByType?: string\n}\n\nexport type FileFilterResult = {\n accepted: File[]\n rejected: File[]\n}\n\n// Composables\nexport const makeFileFilterProps = propsFactory({\n filterByType: String,\n}, 'file-accept')\n\nexport function useFileFilter (props: FileFilterProps) {\n const fileFilter = computed(() => props.filterByType ? createFilter(props.filterByType) : null)\n\n function filterAccepted (files: File[]): FileFilterResult {\n if (fileFilter.value) {\n const accepted = files.filter(fileFilter.value)\n return {\n accepted,\n rejected: files.filter(f => !accepted.includes(f)),\n }\n }\n return {\n accepted: files,\n rejected: [],\n }\n }\n\n return {\n filterAccepted,\n }\n}\n\nfunction createFilter (v: string): ((v: File) => boolean) {\n const types = v.split(',').map(x => x.trim().toLowerCase())\n const extensionsToMatch = types.filter(x => x.startsWith('.'))\n const wildcards = types.filter(x => x.endsWith('/*'))\n const typesToMatch = types.filter(x => !extensionsToMatch.includes(x) && !wildcards.includes(x))\n\n return (file: File): boolean => {\n const extension = file.name.split('.').at(-1)?.toLowerCase() ?? ''\n const typeGroup = file.type.split('/').at(0)?.toLowerCase() ?? ''\n return typesToMatch.includes(file.type) ||\n extensionsToMatch.includes(`.${extension}`) ||\n wildcards.includes(`${typeGroup}/*`)\n }\n}\n"],"mappings":"AAAA;AACA,SAASA,QAAQ,QAAQ,KAAK;AAAA,SACrBC,YAAY;AAWrB;AACA,OAAO,MAAMC,mBAAmB,GAAGD,YAAY,CAAC;EAC9CE,YAAY,EAAEC;AAChB,CAAC,EAAE,aAAa,CAAC;AAEjB,OAAO,SAASC,aAAaA,CAAEC,KAAsB,EAAE;EACrD,MAAMC,UAAU,GAAGP,QAAQ,CAAC,MAAMM,KAAK,CAACH,YAAY,GAAGK,YAAY,CAACF,KAAK,CAACH,YAAY,CAAC,GAAG,IAAI,CAAC;EAE/F,SAASM,cAAcA,CAAEC,KAAa,EAAoB;IACxD,IAAIH,UAAU,CAACI,KAAK,EAAE;MACpB,MAAMC,QAAQ,GAAGF,KAAK,CAACG,MAAM,CAACN,UAAU,CAACI,KAAK,CAAC;MAC/C,OAAO;QACLC,QAAQ;QACRE,QAAQ,EAAEJ,KAAK,CAACG,MAAM,CAACE,CAAC,IAAI,CAACH,QAAQ,CAACI,QAAQ,CAACD,CAAC,CAAC;MACnD,CAAC;IACH;IACA,OAAO;MACLH,QAAQ,EAAEF,KAAK;MACfI,QAAQ,EAAE;IACZ,CAAC;EACH;EAEA,OAAO;IACLL;EACF,CAAC;AACH;AAEA,SAASD,YAAYA,CAAES,CAAS,EAA0B;EACxD,MAAMC,KAAK,GAAGD,CAAC,CAACE,KAAK,CAAC,GAAG,CAAC,CAACC,GAAG,CAACC,CAAC,IAAIA,CAAC,CAACC,IAAI,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC,CAAC;EAC3D,MAAMC,iBAAiB,GAAGN,KAAK,CAACL,MAAM,CAACQ,CAAC,IAAIA,CAAC,CAACI,UAAU,CAAC,GAAG,CAAC,CAAC;EAC9D,MAAMC,SAAS,GAAGR,KAAK,CAACL,MAAM,CAACQ,CAAC,IAAIA,CAAC,CAACM,QAAQ,CAAC,IAAI,CAAC,CAAC;EACrD,MAAMC,YAAY,GAAGV,KAAK,CAACL,MAAM,CAACQ,CAAC,IAAI,CAACG,iBAAiB,CAACR,QAAQ,CAACK,CAAC,CAAC,IAAI,CAACK,SAAS,CAACV,QAAQ,CAACK,CAAC,CAAC,CAAC;EAEhG,OAAQQ,IAAU,IAAc;IAC9B,MAAMC,SAAS,GAAGD,IAAI,CAACE,IAAI,CAACZ,KAAK,CAAC,GAAG,CAAC,CAACa,EAAE,CAAC,CAAC,CAAC,CAAC,EAAET,WAAW,CAAC,CAAC,IAAI,EAAE;IAClE,MAAMU,SAAS,GAAGJ,IAAI,CAACK,IAAI,CAACf,KAAK,CAAC,GAAG,CAAC,CAACa,EAAE,CAAC,CAAC,CAAC,EAAET,WAAW,CAAC,CAAC,IAAI,EAAE;IACjE,OAAOK,YAAY,CAACZ,QAAQ,CAACa,IAAI,CAACK,IAAI,CAAC,IACrCV,iBAAiB,CAACR,QAAQ,CAAC,IAAIc,SAAS,EAAE,CAAC,IAC3CJ,SAAS,CAACV,QAAQ,CAAC,GAAGiB,SAAS,IAAI,CAAC;EACxC,CAAC;AACH","ignoreList":[]}
+85
View File
@@ -0,0 +1,85 @@
import type { PropType, Ref } from 'vue';
import type { MaybeRef } from '../util/index.js';
/**
* - boolean: match without highlight
* - number: single match (index), length already known
* - []: single match (start, end)
* - [][]: multiple matches (start, end), shouldn't overlap
*/
export type FilterMatchArraySingle = readonly [number, number];
export type FilterMatchArrayMultiple = readonly FilterMatchArraySingle[];
export type FilterMatchArray = FilterMatchArraySingle | FilterMatchArrayMultiple;
export type FilterMatch = boolean | number | FilterMatchArray;
export type FilterFunction = (value: string, query: string, item?: InternalItem) => FilterMatch;
export type FilterKeyFunctions = Record<string, FilterFunction>;
export type FilterKeys = string | string[];
export type FilterMode = 'some' | 'every' | 'union' | 'intersection';
export interface FilterProps {
customFilter?: FilterFunction;
customKeyFilter?: FilterKeyFunctions;
filterKeys?: FilterKeys;
filterMode?: FilterMode;
noFilter?: boolean;
}
export interface InternalItem<T = any> {
value: any;
raw: T;
type?: string;
}
type FilterResult = {
index: number;
matches: Record<string, FilterMatchArrayMultiple | undefined>;
type?: 'divider' | 'subheader';
};
export declare const defaultFilter: FilterFunction;
export declare const makeFilterProps: <Defaults extends {
customFilter?: unknown;
customKeyFilter?: unknown;
filterKeys?: unknown;
filterMode?: unknown;
noFilter?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
customFilter: unknown extends Defaults["customFilter"] ? PropType<FilterFunction> : {
type: PropType<unknown extends Defaults["customFilter"] ? FilterFunction : FilterFunction | Defaults["customFilter"]>;
default: unknown extends Defaults["customFilter"] ? FilterFunction : FilterFunction | Defaults["customFilter"];
};
customKeyFilter: unknown extends Defaults["customKeyFilter"] ? PropType<FilterKeyFunctions> : {
type: PropType<unknown extends Defaults["customKeyFilter"] ? FilterKeyFunctions : FilterKeyFunctions | Defaults["customKeyFilter"]>;
default: unknown extends Defaults["customKeyFilter"] ? FilterKeyFunctions : FilterKeyFunctions | Defaults["customKeyFilter"];
};
filterKeys: unknown extends Defaults["filterKeys"] ? PropType<FilterKeys> : {
type: PropType<unknown extends Defaults["filterKeys"] ? FilterKeys : Defaults["filterKeys"] | FilterKeys>;
default: unknown extends Defaults["filterKeys"] ? FilterKeys : Defaults["filterKeys"] | NonNullable<FilterKeys>;
};
filterMode: unknown extends Defaults["filterMode"] ? {
type: PropType<FilterMode>;
default: string;
} : Omit<{
type: PropType<FilterMode>;
default: string;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["filterMode"] ? FilterMode : Defaults["filterMode"] | FilterMode>;
default: unknown extends Defaults["filterMode"] ? FilterMode : Defaults["filterMode"] | NonNullable<FilterMode>;
};
noFilter: unknown extends Defaults["noFilter"] ? BooleanConstructor : {
type: PropType<unknown extends Defaults["noFilter"] ? boolean : boolean | Defaults["noFilter"]>;
default: unknown extends Defaults["noFilter"] ? boolean : boolean | Defaults["noFilter"];
};
};
export declare function filterItems(items: readonly (readonly [item: InternalItem, transformed: {}])[] | readonly InternalItem[], query: string, options?: {
customKeyFilter?: FilterKeyFunctions;
default?: FilterFunction;
filterKeys?: FilterKeys;
filterMode?: FilterMode;
noFilter?: boolean;
}): FilterResult[];
export declare function useFilter<T extends InternalItem>(props: FilterProps, items: MaybeRef<T[]>, query: Ref<string | undefined> | (() => string | undefined), options?: {
transform?: (item: T) => {};
customKeyFilter?: MaybeRef<FilterKeyFunctions | undefined>;
}): {
filteredItems: import("vue").ShallowRef<T[], T[]>;
filteredMatches: import("vue").ShallowRef<Map<unknown, Record<string, FilterMatchArrayMultiple | undefined>>, Map<unknown, Record<string, FilterMatchArrayMultiple | undefined>>>;
getMatches: (item: T) => Record<string, FilterMatchArrayMultiple | undefined> | undefined;
};
export declare function highlightResult(name: string, text: string, matches: FilterMatchArrayMultiple | undefined): string | JSX.Element[];
+168
View File
@@ -0,0 +1,168 @@
/* eslint-disable max-statements */
/* eslint-disable no-labels */
// Utilities
import { computed, shallowRef, unref, watchEffect, normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, Fragment as _Fragment } from 'vue';
import { getPropertyFromItem, propsFactory, wrapInArray } from "../util/index.js"; // Types
/**
* - boolean: match without highlight
* - number: single match (index), length already known
* - []: single match (start, end)
* - [][]: multiple matches (start, end), shouldn't overlap
*/
// Composables
export const defaultFilter = (value, query, item) => {
if (value == null || query == null) return -1;
if (!query.length) return 0;
value = value.toString().toLocaleLowerCase();
query = query.toString().toLocaleLowerCase();
const result = [];
let idx = value.indexOf(query);
while (~idx) {
result.push([idx, idx + query.length]);
idx = value.indexOf(query, idx + query.length);
}
return result.length ? result : -1;
};
function normaliseMatch(match, query) {
if (match == null || typeof match === 'boolean' || match === -1) return;
if (typeof match === 'number') return [[match, match + query.length]];
if (Array.isArray(match[0])) return match;
return [match];
}
export const makeFilterProps = propsFactory({
customFilter: Function,
customKeyFilter: Object,
filterKeys: [Array, String],
filterMode: {
type: String,
default: 'intersection'
},
noFilter: Boolean
}, 'filter');
// eslint-disable-next-line complexity
export function filterItems(items, query, options) {
const array = [];
// always ensure we fall back to a functioning filter
const filter = options?.default ?? defaultFilter;
const keys = options?.filterKeys ? wrapInArray(options.filterKeys) : false;
const customFiltersLength = Object.keys(options?.customKeyFilter ?? {}).length;
if (!items?.length) return array;
let lookAheadItems = [];
loop: for (let i = 0; i < items.length; i++) {
const [item, transformed = item] = wrapInArray(items[i]);
const customMatches = {};
const defaultMatches = {};
let match = -1;
if ((query || customFiltersLength > 0) && !options?.noFilter) {
let hasOnlyCustomFilters = false;
if (typeof item === 'object') {
if (item.type === 'divider' || item.type === 'subheader') {
if (lookAheadItems.at(-1)?.type !== 'divider' || item.type !== 'subheader') {
// clear unless, divider appears before subheader
lookAheadItems = [];
}
lookAheadItems.push({
index: i,
matches: {},
type: item.type
});
continue;
}
const filterKeys = keys || Object.keys(transformed);
hasOnlyCustomFilters = filterKeys.length === customFiltersLength;
for (const key of filterKeys) {
const value = getPropertyFromItem(transformed, key);
const keyFilter = options?.customKeyFilter?.[key];
match = keyFilter ? keyFilter(value, query, item) : filter(value, query, item);
if (match !== -1 && match !== false) {
if (keyFilter) customMatches[key] = normaliseMatch(match, query);else defaultMatches[key] = normaliseMatch(match, query);
} else if (options?.filterMode === 'every') {
continue loop;
}
}
} else {
match = filter(item, query, item);
if (match !== -1 && match !== false) {
defaultMatches.title = normaliseMatch(match, query);
}
}
const defaultMatchesLength = Object.keys(defaultMatches).length;
const customMatchesLength = Object.keys(customMatches).length;
if (!defaultMatchesLength && !customMatchesLength) continue;
if (options?.filterMode === 'union' && customMatchesLength !== customFiltersLength && !defaultMatchesLength) continue;
if (options?.filterMode === 'intersection' && (customMatchesLength !== customFiltersLength || !defaultMatchesLength && customFiltersLength > 0 && !hasOnlyCustomFilters)) continue;
}
if (lookAheadItems.length) {
array.push(...lookAheadItems);
lookAheadItems = [];
}
array.push({
index: i,
matches: {
...defaultMatches,
...customMatches
}
});
}
return array;
}
export function useFilter(props, items, query, options) {
const filteredItems = shallowRef([]);
const filteredMatches = shallowRef(new Map());
const transformedItems = computed(() => options?.transform ? unref(items).map(item => [item, options.transform(item)]) : unref(items));
watchEffect(() => {
const _query = typeof query === 'function' ? query() : unref(query);
const strQuery = typeof _query !== 'string' && typeof _query !== 'number' ? '' : String(_query);
const results = filterItems(transformedItems.value, strQuery, {
customKeyFilter: {
...props.customKeyFilter,
...unref(options?.customKeyFilter)
},
default: props.customFilter,
filterKeys: props.filterKeys,
filterMode: props.filterMode,
noFilter: props.noFilter
});
const originalItems = unref(items);
const _filteredItems = [];
const _filteredMatches = new Map();
results.forEach(({
index,
matches
}) => {
const item = originalItems[index];
_filteredItems.push(item);
_filteredMatches.set(item.value, matches);
});
filteredItems.value = _filteredItems;
filteredMatches.value = _filteredMatches;
});
function getMatches(item) {
return filteredMatches.value.get(item.value);
}
return {
filteredItems,
filteredMatches,
getMatches
};
}
export function highlightResult(name, text, matches) {
if (matches == null || !matches.length) return text;
return matches.map((match, i) => {
const start = i === 0 ? 0 : matches[i - 1][1];
const result = [_createElementVNode("span", {
"class": _normalizeClass(`${name}__unmask`)
}, [text.slice(start, match[0])]), _createElementVNode("span", {
"class": _normalizeClass(`${name}__mask`)
}, [text.slice(match[0], match[1])])];
if (i === matches.length - 1) {
result.push(_createElementVNode("span", {
"class": _normalizeClass(`${name}__unmask`)
}, [text.slice(match[1])]));
}
return _createElementVNode(_Fragment, null, [result]);
});
}
//# sourceMappingURL=filter.js.map
File diff suppressed because one or more lines are too long
+29
View File
@@ -0,0 +1,29 @@
export interface FocusProps {
focused: boolean;
'onUpdate:focused': ((focused: boolean) => any) | undefined;
}
export declare const makeFocusProps: <Defaults extends {
focused?: unknown;
'onUpdate:focused'?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
focused: unknown extends Defaults["focused"] ? BooleanConstructor : {
type: import("vue").PropType<unknown extends Defaults["focused"] ? boolean : boolean | Defaults["focused"]>;
default: unknown extends Defaults["focused"] ? boolean : boolean | Defaults["focused"];
};
'onUpdate:focused': unknown extends Defaults["onUpdate:focused"] ? import("vue").PropType<(args_0: boolean) => void> : {
type: import("vue").PropType<unknown extends Defaults["onUpdate:focused"] ? (args_0: boolean) => void : ((args_0: boolean) => void) | Defaults["onUpdate:focused"]>;
default: unknown extends Defaults["onUpdate:focused"] ? (args_0: boolean) => void : ((args_0: boolean) => void) | Defaults["onUpdate:focused"];
};
};
export declare function useFocus(props: FocusProps, name?: string): {
focusClasses: Readonly<import("vue").Ref<{
[x: string]: boolean;
}, {
[x: string]: boolean;
}>>;
isFocused: import("vue").Ref<boolean, boolean> & {
readonly externalValue: boolean;
};
focus: () => void;
blur: () => void;
};
+30
View File
@@ -0,0 +1,30 @@
// Composables
import { useProxiedModel } from "./proxiedModel.js"; // Utilities
import { toRef } from 'vue';
import { EventProp, getCurrentInstanceName, propsFactory } from "../util/index.js"; // Types
// Composables
export const makeFocusProps = propsFactory({
focused: Boolean,
'onUpdate:focused': EventProp()
}, 'focus');
export function useFocus(props, name = getCurrentInstanceName()) {
const isFocused = useProxiedModel(props, 'focused');
const focusClasses = toRef(() => {
return {
[`${name}--focused`]: isFocused.value
};
});
function focus() {
isFocused.value = true;
}
function blur() {
isFocused.value = false;
}
return {
focusClasses,
isFocused,
focus,
blur
};
}
//# sourceMappingURL=focus.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"focus.js","names":["useProxiedModel","toRef","EventProp","getCurrentInstanceName","propsFactory","makeFocusProps","focused","Boolean","useFocus","props","name","isFocused","focusClasses","value","focus","blur"],"sources":["../../src/composables/focus.ts"],"sourcesContent":["// Composables\nimport { useProxiedModel } from '@/composables/proxiedModel'\n\n// Utilities\nimport { toRef } from 'vue'\nimport { EventProp, getCurrentInstanceName, propsFactory } from '@/util'\n\n// Types\nexport interface FocusProps {\n focused: boolean\n 'onUpdate:focused': ((focused: boolean) => any) | undefined\n}\n\n// Composables\nexport const makeFocusProps = propsFactory({\n focused: Boolean,\n 'onUpdate:focused': EventProp<[boolean]>(),\n}, 'focus')\n\nexport function useFocus (\n props: FocusProps,\n name = getCurrentInstanceName()\n) {\n const isFocused = useProxiedModel(props, 'focused')\n const focusClasses = toRef(() => {\n return ({\n [`${name}--focused`]: isFocused.value,\n })\n })\n\n function focus () {\n isFocused.value = true\n }\n\n function blur () {\n isFocused.value = false\n }\n\n return { focusClasses, isFocused, focus, blur }\n}\n"],"mappings":"AAAA;AAAA,SACSA,eAAe,6BAExB;AACA,SAASC,KAAK,QAAQ,KAAK;AAAA,SAClBC,SAAS,EAAEC,sBAAsB,EAAEC,YAAY,4BAExD;AAMA;AACA,OAAO,MAAMC,cAAc,GAAGD,YAAY,CAAC;EACzCE,OAAO,EAAEC,OAAO;EAChB,kBAAkB,EAAEL,SAAS,CAAY;AAC3C,CAAC,EAAE,OAAO,CAAC;AAEX,OAAO,SAASM,QAAQA,CACtBC,KAAiB,EACjBC,IAAI,GAAGP,sBAAsB,CAAC,CAAC,EAC/B;EACA,MAAMQ,SAAS,GAAGX,eAAe,CAACS,KAAK,EAAE,SAAS,CAAC;EACnD,MAAMG,YAAY,GAAGX,KAAK,CAAC,MAAM;IAC/B,OAAQ;MACN,CAAC,GAAGS,IAAI,WAAW,GAAGC,SAAS,CAACE;IAClC,CAAC;EACH,CAAC,CAAC;EAEF,SAASC,KAAKA,CAAA,EAAI;IAChBH,SAAS,CAACE,KAAK,GAAG,IAAI;EACxB;EAEA,SAASE,IAAIA,CAAA,EAAI;IACfJ,SAAS,CAACE,KAAK,GAAG,KAAK;EACzB;EAEA,OAAO;IAAED,YAAY;IAAED,SAAS;IAAEG,KAAK;IAAEC;EAAK,CAAC;AACjD","ignoreList":[]}
+17
View File
@@ -0,0 +1,17 @@
import type { MaybeRefOrGetter, Ref } from 'vue';
import type { VList } from '../components/VList/index.js';
type FocusGroup = {
type: 'list';
contentRef: Ref<VList | undefined>;
displayItemsCount: MaybeRefOrGetter<number>;
} | {
type: 'element';
contentRef: Ref<HTMLElement | undefined>;
};
export declare function useFocusGroups({ groups, onLeave }: {
groups: FocusGroup[];
onLeave: () => void;
}): {
onTabKeydown: (e: KeyboardEvent) => void;
};
+64
View File
@@ -0,0 +1,64 @@
// Utilities
import { toValue } from 'vue';
import { focusableChildren } from "../util/index.js"; // Types
export function useFocusGroups({
groups,
onLeave
}) {
function getContentRef(group) {
return group.type === 'list' ? group.contentRef.value?.$el : group.contentRef.value;
}
function getChildren(group) {
const contentRef = getContentRef(group);
return contentRef ? focusableChildren(contentRef) : [];
}
function onTabKeydown(e) {
const target = e.target;
const direction = e.shiftKey ? 'backward' : 'forward';
const children = groups.map(getChildren);
const currentGroupIndex = groups.map(g => g.type === 'list' ? g.contentRef.value?.$el : g.contentRef.value).findIndex(el => el?.contains(target));
const nextIndex = nextFocusGroup(children, currentGroupIndex, direction, target);
if (nextIndex === null) {
const originGroup = groups[currentGroupIndex];
const origin = children[currentGroupIndex];
const isListGroup = originGroup.type === 'list';
const atEdge = isListGroup || (direction === 'forward' ? origin.at(-1) === e.target : origin.at(0) === e.target);
if (atEdge) {
onLeave();
}
} else {
e.preventDefault();
e.stopImmediatePropagation();
const nextGroup = groups[nextIndex];
if (nextGroup.type === 'list' && toValue(nextGroup.displayItemsCount) > 0) {
nextGroup.contentRef.value?.focus(0);
} else {
const fromBefore = direction === 'forward';
children[nextIndex].at(fromBefore ? 0 : -1).focus();
}
}
}
function nextFocusGroup(children, currentIndex, direction, target) {
const originGroup = groups[currentIndex];
const origin = children[currentIndex];
// List groups always allow leaving (VList manages internal focus)
// Element groups require being at the edge focusable child
if (originGroup.type !== 'list') {
const isAtEdge = direction === 'forward' ? origin.at(-1) === target : origin.at(0) === target;
if (!isAtEdge) return null;
}
const step = direction === 'forward' ? 1 : -1;
for (let i = currentIndex + step; i >= 0 && i < groups.length; i += step) {
const group = groups[i];
if (children[i].length > 0 || group.type === 'list' && toValue(group.displayItemsCount) > 0) {
return i;
}
}
return null;
}
return {
onTabKeydown
};
}
//# sourceMappingURL=focusGroups.js.map
File diff suppressed because one or more lines are too long
+30
View File
@@ -0,0 +1,30 @@
import type { Ref } from 'vue';
export interface FocusTrapProps {
retainFocus: boolean;
captureFocus: boolean;
disableInitialFocus?: boolean;
}
export declare const makeFocusTrapProps: <Defaults extends {
retainFocus?: unknown;
captureFocus?: unknown;
disableInitialFocus?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
retainFocus: unknown extends Defaults["retainFocus"] ? BooleanConstructor : {
type: import("vue").PropType<unknown extends Defaults["retainFocus"] ? boolean : boolean | Defaults["retainFocus"]>;
default: unknown extends Defaults["retainFocus"] ? boolean : boolean | Defaults["retainFocus"];
};
captureFocus: unknown extends Defaults["captureFocus"] ? BooleanConstructor : {
type: import("vue").PropType<unknown extends Defaults["captureFocus"] ? boolean : boolean | Defaults["captureFocus"]>;
default: unknown extends Defaults["captureFocus"] ? boolean : boolean | Defaults["captureFocus"];
};
disableInitialFocus: unknown extends Defaults["disableInitialFocus"] ? BooleanConstructor : {
type: import("vue").PropType<unknown extends Defaults["disableInitialFocus"] ? boolean : boolean | Defaults["disableInitialFocus"]>;
default: unknown extends Defaults["disableInitialFocus"] ? boolean : boolean | Defaults["disableInitialFocus"];
};
};
export declare function useFocusTrap(props: FocusTrapProps, { isActive, localTop, activatorEl, contentEl }: {
isActive: Ref<boolean>;
localTop: Readonly<Ref<boolean>>;
activatorEl?: Readonly<Ref<HTMLElement | undefined>>;
contentEl: Readonly<Ref<HTMLElement | undefined>>;
}): void;
+142
View File
@@ -0,0 +1,142 @@
// Utilities
import { nextTick, onScopeDispose, toRef, toValue, watch } from 'vue';
import { focusableChildren, IN_BROWSER, propsFactory } from "../util/index.js"; // Types
// Types
// Composables
export const makeFocusTrapProps = propsFactory({
retainFocus: Boolean,
captureFocus: Boolean,
/** @deprecated */
disableInitialFocus: Boolean
}, 'focusTrap');
const registry = new Map();
let subscribers = 0;
function onKeydown(e) {
const activeElement = document.activeElement;
if (e.key !== 'Tab' || !activeElement) return;
const parentTraps = Array.from(registry.values()).filter(({
isActive,
contentEl
}) => isActive.value && contentEl.value?.contains(activeElement)).map(x => x.contentEl.value);
let closestTrap;
let currentParent = activeElement.parentElement;
while (currentParent) {
if (parentTraps.includes(currentParent)) {
closestTrap = currentParent;
break;
}
currentParent = currentParent.parentElement;
}
if (!closestTrap) return;
const focusable = focusableChildren(closestTrap)
// excluding VListItems with tabindex="-2"
.filter(x => x.tabIndex >= 0);
if (!focusable.length) return;
const active = document.activeElement;
if (focusable.length === 1 && focusable[0].classList.contains('v-list') && focusable[0].contains(active)) {
e.preventDefault();
return;
}
const firstElement = focusable[0];
const lastElement = focusable[focusable.length - 1];
if (e.shiftKey && (active === firstElement || firstElement.classList.contains('v-list') && firstElement.contains(active))) {
e.preventDefault();
lastElement.focus();
}
if (!e.shiftKey && (active === lastElement || lastElement.classList.contains('v-list') && lastElement.contains(active))) {
e.preventDefault();
firstElement.focus();
}
}
export function useFocusTrap(props, {
isActive,
localTop,
activatorEl,
contentEl
}) {
const trapId = Symbol('trap');
let focusTrapSuppressed = false;
let focusTrapSuppressionTimeout = -1;
async function onPointerdown() {
focusTrapSuppressed = true;
focusTrapSuppressionTimeout = window.setTimeout(() => {
focusTrapSuppressed = false;
}, 100);
}
async function captureOnFocus(e) {
const before = e.relatedTarget;
const after = e.target;
document.removeEventListener('pointerdown', onPointerdown);
document.removeEventListener('keydown', captureOnKeydown);
await nextTick();
if (isActive.value && !focusTrapSuppressed && before !== after && contentEl.value &&
// We're the menu without open submenus or overlays
toValue(localTop) &&
// It isn't the document or the container body
![document, contentEl.value].includes(after) &&
// It isn't inside the container body
!contentEl.value.contains(after)) {
const focusable = focusableChildren(contentEl.value);
focusable[0]?.focus();
}
}
function captureOnKeydown(e) {
if (e.key !== 'Tab') return;
document.removeEventListener('keydown', captureOnKeydown);
if (isActive.value && contentEl.value && e.target && !contentEl.value.contains(e.target)) {
const allFocusableElements = focusableChildren(document.documentElement);
if (e.shiftKey && e.target === allFocusableElements.at(0) || !e.shiftKey && e.target === allFocusableElements.at(-1)) {
const focusable = focusableChildren(contentEl.value);
if (focusable.length > 0) {
e.preventDefault();
focusable[0].focus();
}
}
}
}
const shouldCapture = toRef(() => isActive.value && props.captureFocus && !props.disableInitialFocus);
if (IN_BROWSER) {
watch(() => props.retainFocus, val => {
if (val) {
registry.set(trapId, {
isActive,
contentEl
});
} else {
registry.delete(trapId);
}
}, {
immediate: true
});
watch(shouldCapture, val => {
if (val) {
document.addEventListener('pointerdown', onPointerdown);
document.addEventListener('focusin', captureOnFocus, {
once: true
});
document.addEventListener('keydown', captureOnKeydown);
} else {
document.removeEventListener('pointerdown', onPointerdown);
document.removeEventListener('focusin', captureOnFocus);
document.removeEventListener('keydown', captureOnKeydown);
}
}, {
immediate: true
});
if (subscribers++ < 1) {
document.addEventListener('keydown', onKeydown);
}
}
onScopeDispose(() => {
registry.delete(trapId);
if (!IN_BROWSER) return;
clearTimeout(focusTrapSuppressionTimeout);
document.removeEventListener('pointerdown', onPointerdown);
document.removeEventListener('focusin', captureOnFocus);
document.removeEventListener('keydown', captureOnKeydown);
if (--subscribers < 1) {
document.removeEventListener('keydown', onKeydown);
}
});
}
//# sourceMappingURL=focusTrap.js.map
File diff suppressed because one or more lines are too long
+149
View File
@@ -0,0 +1,149 @@
import type { ComponentInternalInstance, InjectionKey, PropType, Raw, Ref } from 'vue';
import type { ValidationProps } from './validation.js';
import type { EventProp } from '../util/index.js';
export interface FormProvide {
register: (item: {
id: number | string;
vm: ComponentInternalInstance;
validate: () => Promise<string[]>;
reset: () => Promise<void>;
resetValidation: () => Promise<void>;
}) => void;
unregister: (id: number | string) => void;
update: (id: number | string, isValid: boolean | null, errorMessages: string[]) => void;
items: Ref<FormField[]>;
isDisabled: Readonly<Ref<boolean>>;
isReadonly: Readonly<Ref<boolean>>;
isValidating: Ref<boolean>;
isValid: Ref<boolean | null>;
validateOn: Ref<FormProps['validateOn']>;
}
export interface FormField {
id: number | string;
validate: () => Promise<string[]>;
reset: () => Promise<void>;
resetValidation: () => Promise<void>;
vm: Raw<ComponentInternalInstance>;
isValid: boolean | null;
errorMessages: string[];
}
export interface FieldValidationResult {
id: number | string;
errorMessages: string[];
}
export interface FormValidationResult {
valid: boolean;
errors: FieldValidationResult[];
}
export interface SubmitEventPromise extends SubmitEvent, Promise<FormValidationResult> {
}
export declare const FormKey: InjectionKey<FormProvide>;
export interface FormProps {
disabled: boolean;
fastFail: boolean;
readonly: boolean;
modelValue: boolean | null;
'onUpdate:modelValue': EventProp<[boolean | null]> | undefined;
validateOn: ValidationProps['validateOn'];
}
export declare const makeFormProps: <Defaults extends {
disabled?: unknown;
fastFail?: unknown;
readonly?: unknown;
modelValue?: unknown;
validateOn?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
disabled: unknown extends Defaults["disabled"] ? BooleanConstructor : {
type: PropType<unknown extends Defaults["disabled"] ? boolean : boolean | Defaults["disabled"]>;
default: unknown extends Defaults["disabled"] ? boolean : boolean | Defaults["disabled"];
};
fastFail: unknown extends Defaults["fastFail"] ? BooleanConstructor : {
type: PropType<unknown extends Defaults["fastFail"] ? boolean : boolean | Defaults["fastFail"]>;
default: unknown extends Defaults["fastFail"] ? boolean : boolean | Defaults["fastFail"];
};
readonly: unknown extends Defaults["readonly"] ? BooleanConstructor : {
type: PropType<unknown extends Defaults["readonly"] ? boolean : boolean | Defaults["readonly"]>;
default: unknown extends Defaults["readonly"] ? boolean : boolean | Defaults["readonly"];
};
modelValue: unknown extends Defaults["modelValue"] ? {
type: PropType<boolean | null>;
default: null;
} : Omit<{
type: PropType<boolean | null>;
default: null;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["modelValue"] ? boolean | null : boolean | Defaults["modelValue"] | null>;
default: unknown extends Defaults["modelValue"] ? boolean | null : Defaults["modelValue"] | NonNullable<boolean | null>;
};
validateOn: unknown extends Defaults["validateOn"] ? {
type: PropType<FormProps['validateOn']>;
default: string;
} : Omit<{
type: PropType<FormProps['validateOn']>;
default: string;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["validateOn"] ? ("blur eager" | "blur lazy" | "eager" | "eager blur" | "eager input" | "eager invalid-input" | "eager submit" | "input eager" | "input lazy" | "invalid-input eager" | "invalid-input lazy" | "lazy" | "lazy blur" | "lazy input" | "lazy invalid-input" | "lazy submit" | "submit eager" | "submit lazy" | ("blur" | "input" | "invalid-input" | "submit")) | undefined : Defaults["validateOn"] | ("blur eager" | "blur lazy" | "eager" | "eager blur" | "eager input" | "eager invalid-input" | "eager submit" | "input eager" | "input lazy" | "invalid-input eager" | "invalid-input lazy" | "lazy" | "lazy blur" | "lazy input" | "lazy invalid-input" | "lazy submit" | "submit eager" | "submit lazy" | ("blur" | "input" | "invalid-input" | "submit")) | undefined>;
default: unknown extends Defaults["validateOn"] ? ("blur eager" | "blur lazy" | "eager" | "eager blur" | "eager input" | "eager invalid-input" | "eager submit" | "input eager" | "input lazy" | "invalid-input eager" | "invalid-input lazy" | "lazy" | "lazy blur" | "lazy input" | "lazy invalid-input" | "lazy submit" | "submit eager" | "submit lazy" | ("blur" | "input" | "invalid-input" | "submit")) | undefined : Defaults["validateOn"] | NonNullable<("blur eager" | "blur lazy" | "eager" | "eager blur" | "eager input" | "eager invalid-input" | "eager submit" | "input eager" | "input lazy" | "invalid-input eager" | "invalid-input lazy" | "lazy" | "lazy blur" | "lazy input" | "lazy invalid-input" | "lazy submit" | "submit eager" | "submit lazy" | ("blur" | "input" | "invalid-input" | "submit")) | undefined>;
};
};
export declare function createForm(props: FormProps): {
errors: Ref<{
id: number | string;
errorMessages: string[];
}[], FieldValidationResult[] | {
id: number | string;
errorMessages: string[];
}[]>;
isDisabled: Readonly<Ref<boolean, boolean>>;
isReadonly: Readonly<Ref<boolean, boolean>>;
isValidating: import("vue").ShallowRef<boolean, boolean>;
isValid: Ref<boolean | null, boolean | null> & {
readonly externalValue: boolean | null;
};
items: Ref<{
id: number | string;
validate: () => Promise<string[]>;
reset: () => Promise<void>;
resetValidation: () => Promise<void>;
vm: Raw<ComponentInternalInstance>;
isValid: boolean | null;
errorMessages: string[];
}[], FormField[] | {
id: number | string;
validate: () => Promise<string[]>;
reset: () => Promise<void>;
resetValidation: () => Promise<void>;
vm: Raw<ComponentInternalInstance>;
isValid: boolean | null;
errorMessages: string[];
}[]>;
validate: () => Promise<{
valid: boolean;
errors: {
id: number | string;
errorMessages: string[];
}[];
}>;
reset: () => void;
resetValidation: () => void;
};
export declare function useForm(props?: {
readonly: boolean | null;
disabled: boolean | null;
}): {
register?: ((item: {
id: number | string;
vm: ComponentInternalInstance;
validate: () => Promise<string[]>;
reset: () => Promise<void>;
resetValidation: () => Promise<void>;
}) => void) | undefined;
unregister?: ((id: number | string) => void) | undefined;
update?: ((id: number | string, isValid: boolean | null, errorMessages: string[]) => void) | undefined;
items?: Ref<FormField[], FormField[]> | undefined;
isValidating?: Ref<boolean, boolean> | undefined;
isValid?: Ref<boolean | null, boolean | null> | undefined;
validateOn?: Ref<("blur eager" | "blur lazy" | "eager" | "eager blur" | "eager input" | "eager invalid-input" | "eager submit" | "input eager" | "input lazy" | "invalid-input eager" | "invalid-input lazy" | "lazy" | "lazy blur" | "lazy input" | "lazy invalid-input" | "lazy submit" | "submit eager" | "submit lazy" | ("blur" | "input" | "invalid-input" | "submit")) | undefined, ("blur eager" | "blur lazy" | "eager" | "eager blur" | "eager input" | "eager invalid-input" | "eager submit" | "input eager" | "input lazy" | "invalid-input eager" | "invalid-input lazy" | "lazy" | "lazy blur" | "lazy input" | "lazy invalid-input" | "lazy submit" | "submit eager" | "submit lazy" | ("blur" | "input" | "invalid-input" | "submit")) | undefined> | undefined;
isReadonly: import("vue").ComputedRef<boolean>;
isDisabled: import("vue").ComputedRef<boolean>;
};
+133
View File
@@ -0,0 +1,133 @@
// Composables
import { useProxiedModel } from "./proxiedModel.js"; // Utilities
import { computed, inject, markRaw, provide, ref, shallowRef, toRef, watch } from 'vue';
import { consoleWarn, propsFactory } from "../util/index.js"; // Types
export const FormKey = Symbol.for('vuetify:form');
export const makeFormProps = propsFactory({
disabled: Boolean,
fastFail: Boolean,
readonly: Boolean,
modelValue: {
type: Boolean,
default: null
},
validateOn: {
type: String,
default: 'input'
}
}, 'form');
export function createForm(props) {
const model = useProxiedModel(props, 'modelValue');
const isDisabled = toRef(() => props.disabled);
const isReadonly = toRef(() => props.readonly);
const isValidating = shallowRef(false);
const items = ref([]);
const errors = ref([]);
async function validate() {
const results = [];
let valid = true;
errors.value = [];
isValidating.value = true;
for (const item of items.value) {
const itemErrorMessages = await item.validate();
if (itemErrorMessages.length > 0) {
valid = false;
results.push({
id: item.id,
errorMessages: itemErrorMessages
});
}
if (!valid && props.fastFail) break;
}
errors.value = results;
isValidating.value = false;
return {
valid,
errors: errors.value
};
}
function reset() {
items.value.forEach(item => item.reset());
}
function resetValidation() {
items.value.forEach(item => item.resetValidation());
}
watch(items, () => {
let valid = 0;
let invalid = 0;
const results = [];
for (const item of items.value) {
if (item.isValid === false) {
invalid++;
results.push({
id: item.id,
errorMessages: item.errorMessages
});
} else if (item.isValid === true) valid++;
}
errors.value = results;
model.value = invalid > 0 ? false : valid === items.value.length ? true : null;
}, {
deep: true,
flush: 'post'
});
provide(FormKey, {
register: ({
id,
vm,
validate,
reset,
resetValidation
}) => {
if (items.value.some(item => item.id === id)) {
consoleWarn(`Duplicate input name "${id}"`);
}
items.value.push({
id,
validate,
reset,
resetValidation,
vm: markRaw(vm),
isValid: null,
errorMessages: []
});
},
unregister: id => {
items.value = items.value.filter(item => {
return item.id !== id;
});
},
update: (id, isValid, errorMessages) => {
const found = items.value.find(item => item.id === id);
if (!found) return;
found.isValid = isValid;
found.errorMessages = errorMessages;
},
isDisabled,
isReadonly,
isValidating,
isValid: model,
items,
validateOn: toRef(() => props.validateOn)
});
return {
errors,
isDisabled,
isReadonly,
isValidating,
isValid: model,
items,
validate,
reset,
resetValidation
};
}
export function useForm(props) {
const form = inject(FormKey, null);
return {
...form,
isReadonly: computed(() => !!(props?.readonly ?? form?.isReadonly.value)),
isDisabled: computed(() => !!(props?.disabled ?? form?.isDisabled.value))
};
}
//# sourceMappingURL=form.js.map
File diff suppressed because one or more lines are too long
+19
View File
@@ -0,0 +1,19 @@
import type { ComponentOptionsBase, ComponentPublicInstance, Ref, UnwrapRef } from 'vue';
import type { NonEmptyArray, UnionToIntersection } from '../util/index.js';
/** Omit properties starting with P */
type OmitPrefix<T, P extends string, E = Extract<keyof T, `${P}${any}`>> = [E] extends [never] ? T : Omit<T, `${P}${any}`>;
type OmitPrivate<T> = OmitPrefix<T, '$'>;
/** Omit keyof $props from T */
type OmitProps<T> = T extends {
$props: any;
} ? Omit<T, keyof T['$props']> : T;
export declare function forwardRefs<T extends {}, U extends NonEmptyArray<Ref<HTMLElement | Omit<ComponentPublicInstance, '$emit' | '$slots'> | undefined>>, UU = {
[K in keyof U]: NonNullable<UnwrapRef<U[K]>>;
}[number], UC = {
[K in keyof U]: OmitPrivate<OmitProps<NonNullable<UnwrapRef<U[K]>>>>;
}[number], R = T & UnionToIntersection<UC> & {
_allExposed: T | (UU extends {
$options: infer O;
} ? O extends ComponentOptionsBase<any, infer E, any, any, any, any, any, any> ? E : never : never);
}>(target: T, ...refs: U): R;
+94
View File
@@ -0,0 +1,94 @@
// Types
const Refs = Symbol('Forwarded refs');
/** Omit properties starting with P */
/** Omit keyof $props from T */
function getDescriptor(obj, key) {
let currentObj = obj;
while (currentObj) {
const descriptor = Reflect.getOwnPropertyDescriptor(currentObj, key);
if (descriptor) return descriptor;
currentObj = Object.getPrototypeOf(currentObj);
}
return undefined;
}
export function forwardRefs(target, ...refs) {
target[Refs] = refs;
return new Proxy(target, {
get(target, key) {
if (Reflect.has(target, key)) {
return Reflect.get(target, key);
}
// Skip internal properties
if (typeof key === 'symbol' || key.startsWith('$') || key.startsWith('__')) return;
for (const ref of refs) {
if (ref.value && Reflect.has(ref.value, key)) {
const val = Reflect.get(ref.value, key);
return typeof val === 'function' ? val.bind(ref.value) : val;
}
}
},
has(target, key) {
if (Reflect.has(target, key)) {
return true;
}
// Skip internal properties
if (typeof key === 'symbol' || key.startsWith('$') || key.startsWith('__')) return false;
for (const ref of refs) {
if (ref.value && Reflect.has(ref.value, key)) {
return true;
}
}
return false;
},
set(target, key, value) {
if (Reflect.has(target, key)) {
return Reflect.set(target, key, value);
}
// Skip internal properties
if (typeof key === 'symbol' || key.startsWith('$') || key.startsWith('__')) return false;
for (const ref of refs) {
if (ref.value && Reflect.has(ref.value, key)) {
return Reflect.set(ref.value, key, value);
}
}
return false;
},
getOwnPropertyDescriptor(target, key) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
if (descriptor) return descriptor;
// Skip internal properties
if (typeof key === 'symbol' || key.startsWith('$') || key.startsWith('__')) return;
// Check each ref's own properties
for (const ref of refs) {
if (!ref.value) continue;
const descriptor = getDescriptor(ref.value, key) ?? ('_' in ref.value ? getDescriptor(ref.value._?.setupState, key) : undefined);
if (descriptor) return descriptor;
}
// Recursive search up each ref's prototype
for (const ref of refs) {
const childRefs = ref.value && ref.value[Refs];
if (!childRefs) continue;
const queue = childRefs.slice();
while (queue.length) {
const ref = queue.shift();
const descriptor = getDescriptor(ref.value, key);
if (descriptor) return descriptor;
const childRefs = ref.value && ref.value[Refs];
if (childRefs) queue.push(...childRefs);
}
}
return undefined;
}
});
}
//# sourceMappingURL=forwardRefs.js.map
File diff suppressed because one or more lines are too long
+23
View File
@@ -0,0 +1,23 @@
import type { ComponentPublicInstance, InjectionKey, Ref } from 'vue';
import type { LocaleInstance, RtlInstance } from './locale.js';
import type { EasingFunction } from '../util/index.js';
export interface GoToInstance {
rtl: Ref<boolean>;
options: InternalGoToOptions;
}
export interface InternalGoToOptions {
container: ComponentPublicInstance | HTMLElement | string;
duration: number;
layout: boolean;
offset: number;
easing: string | EasingFunction;
patterns: Record<string, EasingFunction>;
}
export type GoToOptions = Partial<InternalGoToOptions>;
export declare const GoToSymbol: InjectionKey<GoToInstance>;
export declare function createGoTo(options: GoToOptions | undefined, locale: LocaleInstance & RtlInstance): GoToInstance;
export declare function scrollTo(_target: ComponentPublicInstance | HTMLElement | number | string, _options: GoToOptions, horizontal?: boolean, goTo?: GoToInstance): Promise<unknown>;
export declare function useGoTo(_options?: GoToOptions): {
(target: ComponentPublicInstance | HTMLElement | string | number, options?: Partial<GoToOptions>): Promise<unknown>;
horizontal: (target: ComponentPublicInstance | HTMLElement | string | number, options?: Partial<GoToOptions>) => Promise<unknown>;
};
+125
View File
@@ -0,0 +1,125 @@
// Utilities
import { inject, toRef } from 'vue';
import { useRtl } from "./locale.js";
import { clamp, consoleWarn, easingPatterns, mergeDeep, PREFERS_REDUCED_MOTION, refElement } from "../util/index.js"; // Types
export const GoToSymbol = Symbol.for('vuetify:goto');
function genDefaults() {
return {
container: undefined,
duration: 300,
layout: false,
offset: 0,
easing: 'easeInOutCubic',
patterns: easingPatterns
};
}
function getContainer(el) {
return getTarget(el) ?? (document.scrollingElement || document.body);
}
function getTarget(el) {
return typeof el === 'string' ? document.querySelector(el) : refElement(el);
}
function getOffset(target, horizontal, rtl) {
if (typeof target === 'number') return horizontal && rtl ? -target : target;
let el = getTarget(target);
let totalOffset = 0;
while (el) {
totalOffset += horizontal ? el.offsetLeft : el.offsetTop;
el = el.offsetParent;
}
return totalOffset;
}
export function createGoTo(options, locale) {
return {
rtl: locale.isRtl,
options: mergeDeep(genDefaults(), options)
};
}
export async function scrollTo(_target, _options, horizontal, goTo) {
const property = horizontal ? 'scrollLeft' : 'scrollTop';
const options = mergeDeep(goTo?.options ?? genDefaults(), _options);
const rtl = goTo?.rtl.value;
const target = (typeof _target === 'number' ? _target : getTarget(_target)) ?? 0;
const container = options.container === 'parent' && target instanceof HTMLElement ? target.parentElement : getContainer(options.container);
const ease = PREFERS_REDUCED_MOTION() ? options.patterns.instant : typeof options.easing === 'function' ? options.easing : options.patterns[options.easing];
if (!ease) throw new TypeError(`Easing function "${options.easing}" not found.`);
let targetLocation;
if (typeof target === 'number') {
targetLocation = getOffset(target, horizontal, rtl);
} else {
targetLocation = getOffset(target, horizontal, rtl) - getOffset(container, horizontal, rtl);
if (options.layout) {
const styles = window.getComputedStyle(target);
const layoutOffset = styles.getPropertyValue('--v-layout-top');
if (layoutOffset) targetLocation -= parseInt(layoutOffset, 10);
}
}
targetLocation += options.offset;
targetLocation = clampTarget(container, targetLocation, !!rtl, !!horizontal);
const startLocation = container[property] ?? 0;
if (targetLocation === startLocation) return Promise.resolve(targetLocation);
const startTime = performance.now();
return new Promise(resolve => requestAnimationFrame(function step(currentTime) {
const timeElapsed = currentTime - startTime;
const progress = timeElapsed / options.duration;
const location = Math.floor(startLocation + (targetLocation - startLocation) * ease(clamp(progress, 0, 1)));
container[property] = location;
// Allow for some jitter if target time has elapsed
if (progress >= 1 && Math.abs(location - container[property]) < 10) {
return resolve(targetLocation);
} else if (progress > 2) {
// The target might not be reachable
consoleWarn('Scroll target is not reachable');
return resolve(container[property]);
}
requestAnimationFrame(step);
}));
}
export function useGoTo(_options = {}) {
const goToInstance = inject(GoToSymbol);
const {
isRtl
} = useRtl();
if (!goToInstance) throw new Error('[Vuetify] Could not find injected goto instance');
const goTo = {
...goToInstance,
// can be set via VLocaleProvider
rtl: toRef(() => goToInstance.rtl.value || isRtl.value)
};
async function go(target, options) {
return scrollTo(target, mergeDeep(_options, options), false, goTo);
}
go.horizontal = async (target, options) => {
return scrollTo(target, mergeDeep(_options, options), true, goTo);
};
return go;
}
/**
* Clamp target value to achieve a smooth scroll animation
* when the value goes outside the scroll container size
*/
function clampTarget(container, value, rtl, horizontal) {
const {
scrollWidth,
scrollHeight
} = container;
const [containerWidth, containerHeight] = container === document.scrollingElement ? [window.innerWidth, window.innerHeight] : [container.offsetWidth, container.offsetHeight];
let min;
let max;
if (horizontal) {
if (rtl) {
min = -(scrollWidth - containerWidth);
max = 0;
} else {
min = 0;
max = scrollWidth - containerWidth;
}
} else {
min = 0;
max = scrollHeight + -containerHeight;
}
return clamp(value, min, max);
}
//# sourceMappingURL=goto.js.map
File diff suppressed because one or more lines are too long
+113
View File
@@ -0,0 +1,113 @@
import type { ComponentInternalInstance, ExtractPropTypes, InjectionKey, PropType, Ref } from 'vue';
import type { EventProp } from '../util/index.js';
export interface GroupItem {
id: string;
value: Ref<unknown>;
disabled: Ref<boolean | undefined>;
useIndexAsValue?: boolean;
}
export interface GroupProps {
disabled: boolean;
modelValue: unknown;
multiple?: boolean;
mandatory?: boolean | 'force' | undefined;
max?: number | undefined;
selectedClass: string | undefined;
'onUpdate:modelValue': EventProp<[unknown]> | undefined;
}
export interface GroupProvide {
register: (item: GroupItem, cmp: ComponentInternalInstance) => void;
unregister: (id: string) => void;
select: (id: string, value: boolean) => void;
selected: Ref<Readonly<string[]>>;
isSelected: (id: string) => boolean;
prev: () => void;
next: () => void;
selectedClass: Ref<string | undefined>;
items: Readonly<Ref<{
id: string;
value: unknown;
disabled: boolean | undefined;
}[]>>;
disabled: Ref<boolean | undefined>;
getItemIndex: (value: unknown) => number;
}
export interface GroupItemProvide {
id: string;
isSelected: Ref<boolean>;
isFirst: Ref<boolean>;
isLast: Ref<boolean>;
toggle: () => void;
select: (value: boolean) => void;
selectedClass: Ref<(string | undefined)[] | false>;
value: Ref<unknown>;
disabled: Ref<boolean | undefined>;
group: GroupProvide;
register: () => void;
unregister: () => void;
}
export declare const makeGroupProps: <Defaults extends {
modelValue?: unknown;
multiple?: unknown;
mandatory?: unknown;
max?: unknown;
selectedClass?: unknown;
disabled?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
modelValue: unknown extends Defaults["modelValue"] ? {
type: null;
default: undefined;
} : Omit<{
type: null;
default: undefined;
}, "default" | "type"> & {
type: PropType<unknown extends Defaults["modelValue"] ? any : any>;
default: unknown extends Defaults["modelValue"] ? any : any;
};
multiple: unknown extends Defaults["multiple"] ? BooleanConstructor : {
type: PropType<unknown extends Defaults["multiple"] ? boolean : boolean | Defaults["multiple"]>;
default: unknown extends Defaults["multiple"] ? boolean : boolean | Defaults["multiple"];
};
mandatory: unknown extends Defaults["mandatory"] ? PropType<"force" | boolean> : {
type: PropType<unknown extends Defaults["mandatory"] ? "force" | boolean : "force" | boolean | Defaults["mandatory"]>;
default: unknown extends Defaults["mandatory"] ? "force" | boolean : Defaults["mandatory"] | NonNullable<"force" | boolean>;
};
max: unknown extends Defaults["max"] ? NumberConstructor : {
type: PropType<unknown extends Defaults["max"] ? number : number | Defaults["max"]>;
default: unknown extends Defaults["max"] ? number : number | Defaults["max"];
};
selectedClass: unknown extends Defaults["selectedClass"] ? StringConstructor : {
type: PropType<unknown extends Defaults["selectedClass"] ? string : string | Defaults["selectedClass"]>;
default: unknown extends Defaults["selectedClass"] ? string : string | Defaults["selectedClass"];
};
disabled: unknown extends Defaults["disabled"] ? BooleanConstructor : {
type: PropType<unknown extends Defaults["disabled"] ? boolean : boolean | Defaults["disabled"]>;
default: unknown extends Defaults["disabled"] ? boolean : boolean | Defaults["disabled"];
};
};
export declare const makeGroupItemProps: <Defaults extends {
value?: unknown;
disabled?: unknown;
selectedClass?: unknown;
} = {}>(defaults?: Defaults | undefined) => {
value: unknown extends Defaults["value"] ? null : {
type: PropType<unknown extends Defaults["value"] ? any : any>;
default: unknown extends Defaults["value"] ? any : any;
};
disabled: unknown extends Defaults["disabled"] ? BooleanConstructor : {
type: PropType<unknown extends Defaults["disabled"] ? boolean : boolean | Defaults["disabled"]>;
default: unknown extends Defaults["disabled"] ? boolean : boolean | Defaults["disabled"];
};
selectedClass: unknown extends Defaults["selectedClass"] ? StringConstructor : {
type: PropType<unknown extends Defaults["selectedClass"] ? string : string | Defaults["selectedClass"]>;
default: unknown extends Defaults["selectedClass"] ? string : string | Defaults["selectedClass"];
};
};
export interface GroupItemProps extends ExtractPropTypes<ReturnType<typeof makeGroupItemProps>> {
'onGroup:selected': EventProp<[{
value: boolean;
}]> | undefined;
}
export declare function useGroupItem(props: GroupItemProps, injectKey: InjectionKey<GroupProvide>, required?: true): GroupItemProvide;
export declare function useGroupItem(props: GroupItemProps, injectKey: InjectionKey<GroupProvide>, required: false): GroupItemProvide | null;
export declare function useGroup(props: GroupProps, injectKey: InjectionKey<GroupProvide>): GroupProvide;
+232
View File
@@ -0,0 +1,232 @@
// Composables
import { useProxiedModel } from "./proxiedModel.js"; // Utilities
import { computed, inject, onBeforeUnmount, onMounted, onUpdated, provide, reactive, toRef, unref, useId, watch } from 'vue';
import { consoleWarn, deepEqual, findChildrenWithProvide, getCurrentInstance, propsFactory, wrapInArray } from "../util/index.js"; // Types
export const makeGroupProps = propsFactory({
modelValue: {
type: null,
default: undefined
},
multiple: Boolean,
mandatory: [Boolean, String],
max: Number,
selectedClass: String,
disabled: Boolean
}, 'group');
export const makeGroupItemProps = propsFactory({
value: null,
disabled: Boolean,
selectedClass: String
}, 'group-item');
// Composables
export function useGroupItem(props, injectKey, required = true) {
const vm = getCurrentInstance('useGroupItem');
if (!vm) {
throw new Error('[Vuetify] useGroupItem composable must be used inside a component setup function');
}
const id = useId();
provide(Symbol.for(`${injectKey.description}:id`), id);
const group = inject(injectKey, null);
if (!group) {
if (!required) return group;
throw new Error(`[Vuetify] Could not find useGroup injection with symbol ${injectKey.description}`);
}
const value = toRef(() => props.value);
const disabled = computed(() => !!(group.disabled.value || props.disabled));
function register() {
group?.register({
id,
value,
disabled
}, vm);
}
function unregister() {
group?.unregister(id);
}
register();
onBeforeUnmount(() => unregister());
const isSelected = computed(() => {
return group.isSelected(id);
});
const isFirst = computed(() => {
return group.items.value[0].id === id;
});
const isLast = computed(() => {
return group.items.value[group.items.value.length - 1].id === id;
});
const selectedClass = computed(() => isSelected.value && [group.selectedClass.value, props.selectedClass]);
watch(isSelected, value => {
vm.emit('group:selected', {
value
});
}, {
flush: 'sync'
});
return {
id,
isSelected,
isFirst,
isLast,
toggle: () => group.select(id, !isSelected.value),
select: value => group.select(id, value),
selectedClass,
value,
disabled,
group,
register,
unregister
};
}
export function useGroup(props, injectKey) {
let isUnmounted = false;
const items = reactive([]);
const selected = useProxiedModel(props, 'modelValue', [], v => {
if (v === undefined) return [];
return getIds(items, v === null ? [null] : wrapInArray(v));
}, v => {
const arr = getValues(items, v);
return props.multiple ? arr : arr[0];
});
const groupVm = getCurrentInstance('useGroup');
function register(item, vm) {
// Is there a better way to fix this typing?
const unwrapped = item;
const key = Symbol.for(`${injectKey.description}:id`);
const children = findChildrenWithProvide(key, groupVm?.vnode);
const index = children.indexOf(vm);
if (unref(unwrapped.value) === undefined) {
unwrapped.value = index;
unwrapped.useIndexAsValue = true;
}
if (index > -1) {
items.splice(index, 0, unwrapped);
} else {
items.push(unwrapped);
}
}
function unregister(id) {
if (isUnmounted) return;
// TODO: re-evaluate this line's importance in the future
// should we only modify the model if mandatory is set.
// selected.value = selected.value.filter(v => v !== id)
forceMandatoryValue();
const index = items.findIndex(item => item.id === id);
items.splice(index, 1);
}
// If mandatory and nothing is selected, then select first non-disabled item
function forceMandatoryValue() {
const item = items.find(item => !item.disabled);
if (item && props.mandatory === 'force' && !selected.value.length) {
selected.value = [item.id];
}
}
onMounted(() => {
forceMandatoryValue();
});
onBeforeUnmount(() => {
isUnmounted = true;
});
onUpdated(() => {
// #19655 update the items that use the index as the value.
for (let i = 0; i < items.length; i++) {
if (items[i].useIndexAsValue) {
items[i].value = i;
}
}
});
function select(id, value) {
const item = items.find(item => item.id === id);
if (value && item?.disabled) return;
if (props.multiple) {
const internalValue = selected.value.slice();
const index = internalValue.findIndex(v => v === id);
const isSelected = ~index;
value = value ?? !isSelected;
// We can't remove value if group is
// mandatory, value already exists,
// and it is the only value
if (isSelected && props.mandatory && internalValue.length <= 1) return;
// We can't add value if it would
// cause max limit to be exceeded
if (!isSelected && props.max != null && internalValue.length + 1 > props.max) return;
if (index < 0 && value) internalValue.push(id);else if (index >= 0 && !value) internalValue.splice(index, 1);
selected.value = internalValue;
} else {
const isSelected = selected.value.includes(id);
if (props.mandatory && isSelected) return;
if (!isSelected && !value) return;
selected.value = value ?? !isSelected ? [id] : [];
}
}
function step(offset) {
// getting an offset from selected value obviously won't work with multiple values
if (props.multiple) consoleWarn('This method is not supported when using "multiple" prop');
if (!selected.value.length) {
const item = items.find(item => !item.disabled);
item && (selected.value = [item.id]);
} else {
const currentId = selected.value[0];
const currentIndex = items.findIndex(i => i.id === currentId);
let newIndex = (currentIndex + offset) % items.length;
let newItem = items[newIndex];
while (newItem.disabled && newIndex !== currentIndex) {
newIndex = (newIndex + offset) % items.length;
newItem = items[newIndex];
}
if (newItem.disabled) return;
selected.value = [items[newIndex].id];
}
}
const state = {
register,
unregister,
selected,
select,
disabled: toRef(() => props.disabled),
prev: () => step(items.length - 1),
next: () => step(1),
isSelected: id => selected.value.includes(id),
selectedClass: toRef(() => props.selectedClass),
items: toRef(() => items),
getItemIndex: value => getItemIndex(items, value)
};
provide(injectKey, state);
return state;
}
function getItemIndex(items, value) {
const ids = getIds(items, [value]);
if (!ids.length) return -1;
return items.findIndex(item => item.id === ids[0]);
}
function getIds(items, modelValue) {
const ids = [];
modelValue.forEach(value => {
const item = items.find(item => deepEqual(value, item.value));
const itemByIndex = items[value];
if (item?.value !== undefined) {
ids.push(item.id);
} else if (itemByIndex?.useIndexAsValue) {
ids.push(itemByIndex.id);
}
});
return ids;
}
function getValues(items, ids) {
const values = [];
ids.forEach(id => {
const itemIndex = items.findIndex(item => item.id === id);
if (~itemIndex) {
const item = items[itemIndex];
values.push(item.value !== undefined ? item.value : itemIndex);
}
});
return values;
}
//# sourceMappingURL=group.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,25 @@
export type KeyCombination = Sequence | Alternate | Combo | Key;
export interface Sequence {
type: 'sequence';
parts: (Alternate | Combo | Key)[];
}
export interface Alternate {
type: 'alternate';
parts: (Combo | Key)[];
}
export interface Combo {
type: 'combo';
parts: Key[];
}
export type Key = string;
/**
* Splits a single combination string into individual key parts.
* Grammar:
*
* sequence = alternate *('-' alternate)
* alternate = combo *('/' combo)
* combo = key *(('+' | '_') key)
* key = /./ *(/[^-/+_ ]/)
*
*/
export declare function parseKeyCombination(input: string): KeyCombination;
+110
View File
@@ -0,0 +1,110 @@
// Utilities
import { normalizeKey } from "./key-aliases.js";
import { consoleWarn, includes } from "../../util/index.js"; // Types
class ParseError extends Error {}
/**
* Splits a single combination string into individual key parts.
* Grammar:
*
* sequence = alternate *('-' alternate)
* alternate = combo *('/' combo)
* combo = key *(('+' | '_') key)
* key = /./ *(/[^-/+_ ]/)
*
*/
export function parseKeyCombination(input) {
let pos = 0;
try {
const result = parseSequence();
if (!atEnd()) {
throw new ParseError(`Unexpected character '${peek()}' at position ${pos}`);
}
return result;
} catch (err) {
if (err instanceof ParseError) {
consoleWarn(`Invalid hotkey combination: ${err.message}\n ${input}\n ${' '.repeat(pos)}^`);
return '';
} else {
throw err;
}
}
function peek(ahead = 0) {
return pos + ahead < input.length ? input[pos + ahead] : null;
}
function consume() {
if (pos >= input.length) {
throw new ParseError('Unexpected end of input');
}
return input[pos++];
}
function atEnd() {
return pos >= input.length;
}
// sequence = alternate *('-' alternate)
function parseSequence() {
const parts = [parseAlternate()];
while (peek() === '-') {
consume();
parts.push(parseAlternate());
}
if (parts.length === 1) return parts[0];
return {
type: 'sequence',
parts
};
}
// alternate = combo *('/' combo)
function parseAlternate() {
const parts = [parseCombo()];
while (peek() === '/') {
consume();
parts.push(parseCombo());
}
if (parts.length === 1) return parts[0];
return {
type: 'alternate',
parts
};
}
// combo = key *(('+' | '_') key)
function parseCombo() {
const keys = [parseKey()];
while (includes(['+', '_'], peek())) {
consume();
keys.push(parseKey());
}
if (keys.length === 1) return keys[0];
return {
type: 'combo',
parts: keys
};
}
// key = /./ *(/[^-/+_ ]/)
function parseKey() {
const ch = peek();
if (ch == null) {
throw new ParseError('Unexpected end of input');
}
const next = peek(1);
if (isSep(ch) && next != null && !isSep(next)) {
throw new ParseError(`Unexpected separator '${ch}' at position ${pos}`);
}
const first = consume();
// separator keys are always a single character
if (isSep(first)) return first;
const chars = [first];
while (!atEnd() && !isSep(peek()) && peek() !== ' ') {
chars.push(consume());
}
return normalizeKey(chars.join(''));
}
}
function isSep(char) {
return includes(['-', '/', '+', '_'], char);
}
//# sourceMappingURL=hotkey-parsing.js.map
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
import type { MaybeRef } from '../../util/index.js';
interface HotkeyOptions {
event?: MaybeRef<'keydown' | 'keyup'>;
inputs?: MaybeRef<boolean>;
preventDefault?: MaybeRef<boolean>;
sequenceTimeout?: MaybeRef<number>;
}
export declare function useHotkey(keys: MaybeRef<string | undefined>, callback: (e: KeyboardEvent) => void, options?: HotkeyOptions): () => void;
+115
View File
@@ -0,0 +1,115 @@
// Composables
import { parseKeyCombination } from "./hotkey-parsing.js"; // Utilities
import { onScopeDispose, toValue, watch } from 'vue';
import { IN_BROWSER } from "../../util/index.js"; // Types
const MODIFIERS = ['ctrl', 'shift', 'alt', 'meta', 'cmd'];
const modifiersSet = new Set(MODIFIERS);
function isModifier(key) {
return modifiersSet.has(key);
}
const emptyModifiers = Object.fromEntries(MODIFIERS.map(m => [m, false]));
export function useHotkey(keys, callback, options = {}) {
if (!IN_BROWSER) return function () {};
const {
event = 'keydown',
inputs = false,
preventDefault = true,
sequenceTimeout = 1000
} = options;
const isMac = navigator?.userAgent?.includes('Macintosh') ?? false;
let timeout = 0;
let keyGroups;
let isSequence = false;
let groupIndex = 0;
function isInputFocused() {
if (toValue(inputs)) return false;
const activeElement = document.activeElement;
return activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable || activeElement.contentEditable === 'true');
}
function resetSequence() {
groupIndex = 0;
clearTimeout(timeout);
}
function handler(e) {
const group = keyGroups[groupIndex];
if (!group || isInputFocused()) return;
if (!matchesKeyGroup(e, group, isMac)) {
if (isSequence) resetSequence();
return;
}
if (toValue(preventDefault)) e.preventDefault();
if (!isSequence) {
callback(e);
return;
}
clearTimeout(timeout);
groupIndex++;
if (groupIndex === keyGroups.length) {
callback(e);
resetSequence();
return;
}
timeout = window.setTimeout(resetSequence, toValue(sequenceTimeout));
}
function cleanup() {
window.removeEventListener(toValue(event), handler);
clearTimeout(timeout);
}
watch(() => toValue(keys), newKeys => {
cleanup();
if (newKeys) {
const parsed = parseKeyCombination(newKeys.toLowerCase());
if (parsed) {
const parts = typeof parsed !== 'string' && parsed.type === 'sequence' ? parsed.parts : [parsed];
isSequence = parts.length > 1;
keyGroups = parts;
resetSequence();
window.addEventListener(toValue(event), handler);
}
}
}, {
immediate: true
});
// Watch for changes in the event type to re-register the listener
watch(() => toValue(event), (newEvent, oldEvent) => {
if (oldEvent && keyGroups && keyGroups.length > 0) {
window.removeEventListener(oldEvent, handler);
window.addEventListener(newEvent, handler);
}
});
onScopeDispose(cleanup, true);
return cleanup;
}
function matchesKeyGroup(e, group, isMac) {
if (typeof group !== 'string' && group.type === 'alternate') {
return group.parts.some(part => matchesKeyGroup(e, part, isMac));
}
const {
modifiers,
actualKey
} = parseKeyGroup(group);
const expectCtrl = modifiers.ctrl || !isMac && (modifiers.cmd || modifiers.meta);
const expectMeta = isMac && (modifiers.cmd || modifiers.meta);
return e.ctrlKey === expectCtrl && e.metaKey === expectMeta && e.shiftKey === modifiers.shift && e.altKey === modifiers.alt && e.key.toLowerCase() === actualKey?.toLowerCase();
}
function parseKeyGroup(group) {
const parts = typeof group === 'string' ? [group] : group.parts;
const modifiers = {
...emptyModifiers
};
let actualKey;
for (const part of parts) {
if (isModifier(part)) {
modifiers[part] = true;
} else {
// TODO: handle multiple keys
actualKey = part;
}
}
return {
modifiers,
actualKey
};
}
//# sourceMappingURL=hotkey.js.map
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export { useHotkey } from './hotkey.js';
+2
View File
@@ -0,0 +1,2 @@
export { useHotkey } from "./hotkey.js";
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","names":["useHotkey"],"sources":["../../../src/composables/hotkey/index.ts"],"sourcesContent":["export { useHotkey } from './hotkey'\n"],"mappings":"SAASA,SAAS","ignoreList":[]}
+14
View File
@@ -0,0 +1,14 @@
/**
* Centralized key alias mapping for consistent key normalization across the hotkey system.
*
* This maps various user-friendly aliases to canonical key names that match
* KeyboardEvent.key values (in lowercase) where possible.
*/
export declare const keyAliasMap: Record<string, string>;
/**
* Normalizes a key string to its canonical form using the alias map.
*
* @param key - The key string to normalize
* @returns The canonical key name in lowercase
*/
export declare function normalizeKey(key: string): string;

Some files were not shown because too many files have changed in this diff Show More