').css({ position: 'relative', display: 'inline-block', width: '100%' });
+ $input.wrap($wrapper);
+
+ const $dropdown = $('
').css({
+ position: 'absolute',
+ top: $input.outerHeight(),
+ left: 0,
+ right: 0,
+ maxHeight: 0,
+ overflowY: 'auto',
+ background: 'var(--bg-color, #fff)',
+ border: '1px solid #ccc',
+ borderTop: 'none',
+ borderRadius: '0 0 4px 4px',
+ zIndex: 1000,
+ opacity: 0,
+ boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
+ transition: 'max-height .2s ease, opacity .2s ease',
+ pointerEvents: 'none',
+ }).addClass('time-picker-dropdown');
+
+ for (let h = 0; h < 24; h++) {
+ for (let m = 0; m < 60; m += interval) {
+ const time = `${padTwo(h)}:${padTwo(m)}`;
+ const $item = $('
').text(time).css({
+ padding: '6px 12px',
+ cursor: 'pointer',
+ }).on('mousedown', (e) => {
+ e.preventDefault();
+ $input.val(time).trigger('change');
+ closeDropdown();
+ }).on('mouseenter', function () {
+ $(this).css('background', 'var(--highlight-color, #e8f0fe)');
+ }).on('mouseleave', function () {
+ $(this).css('background', '');
+ });
+ $dropdown.append($item);
+ }
+ }
+
+ let isOpen = false;
+
+ function openDropdown() {
+ if (isOpen) return;
+ isOpen = true;
+ $dropdown.css({ maxHeight: '200px', opacity: 1, pointerEvents: 'auto' });
+ const val = $input.val() as string;
+ if (val) {
+ $dropdown.children().each(function cb() {
+ if ($(this).text() !== val) return;
+ const el = this as HTMLElement;
+ el.parentElement!.scrollTop = el.offsetTop - el.parentElement!.offsetTop - 80;
+ });
+ }
+ }
+
+ function closeDropdown() {
+ if (!isOpen) return;
+ isOpen = false;
+ $dropdown.css({ maxHeight: 0, opacity: 0, pointerEvents: 'none' });
+ }
+
+ $input.after($dropdown);
+ $input.on('focus', openDropdown);
+ // Only close when the element actually loses focus within the page,
+ // not when the whole window loses focus (alt-tab)
+ $input.on('blur', () => {
+ if (document.hasFocus()) closeDropdown();
+ });
+}
const datepickerPage = new AutoloadPage('datepickerPage', async () => {
$('[data-pick-date]').each(function () {
flatpickr(this, { allowInput: true });
});
$('[data-pick-time]').each(function () {
- const $this = $(this);
- if (!$this.data('pickatime')) {
- // eslint-disable-next-line no-new
- new Picker(this, 'pickatime', TimePicker, {
- format: 'H:i',
- editable: true,
- interval: 15,
- clear: false,
- onSet() { $this.trigger('change'); },
- });
- }
+ initTimePicker(this as HTMLInputElement);
});
});
diff --git a/packages/ui-default/components/datepicker/datepicker.styl b/packages/ui-default/components/datepicker/datepicker.styl
deleted file mode 100644
index 68e155e70..000000000
--- a/packages/ui-default/components/datepicker/datepicker.styl
+++ /dev/null
@@ -1,53 +0,0 @@
-.picker__holder
- margin-top: -1rem
- border-radius: 0
- transform: scale(0.9) translateZ(0)
- transform-origin: 0 0
- outline: 0
- font-size: rem($font-size-secondary)
- transition: transform .1s ease-in-out,
- opacity .1s ease-in-out,
- max-height 0s .1s,
- border-width 0s .1s
-
-.picker--opened .picker__holder
- transform: scale(1) translateZ(0)
- transition: transform .1s ease-in-out,
- opacity .1s ease-in-out,
- max-height 0s,
- border-width 0s
-
-.picker__list
- padding: 0
-
-.picker__list-item
- border: 0
-
-.picker__list-item:hover
- background: $datapicker-list-item-bg-color-hover
-
-.picker__list-item--selected, .picker__list-item--selected:hover, .picker--focused .picker__list-item--selected
- background: $primary-color
-
-.picker__day--infocus:hover, .picker__day--outfocus:hover
- background: $datapicker-list-item-bg-color-hover
-
-.picker__day--highlighted
- border-color: transparent
-
-.picker__day--highlighted:hover, .picker--focused .picker__day--highlighted
- background: transparent
-
-.picker__day--selected, .picker__day--selected:hover, .picker--focused .picker__day--selected
- background: $primary-color
-
-.picker__button--today:hover, .picker__button--clear:hover, .picker__button--close:hover
- background: $datapicker-list-item-bg-color-hover
- border-color: $datapicker-list-item-bg-color-hover
-
-.picker__button--today:focus, .picker__button--clear:focus, .picker__button--close:focus
- background: $datapicker-list-item-bg-color-hover
- border-color: $datapicker-list-item-bg-color-hover
-
-.picker__nav--prev:hover, .picker__nav--next:hover
- background: $datapicker-list-item-bg-color-hover
diff --git a/packages/ui-default/components/datepicker/timepicker.ts b/packages/ui-default/components/datepicker/timepicker.ts
deleted file mode 100644
index c891dffc2..000000000
--- a/packages/ui-default/components/datepicker/timepicker.ts
+++ /dev/null
@@ -1,307 +0,0 @@
-/* eslint-disable */
-import Picker from 'pickadate/lib/picker';
-
-const HOURS_IN_DAY = 24;
-const MINUTES_IN_HOUR = 60;
-const MINUTES_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR;
-const { _ } = Picker;
-export default class TimePicker {
- queue = {
- interval: 'i',
- min: 'measure create',
- max: 'measure create',
- now: 'now create',
- select: 'parse create',
- highlight: 'parse create',
- view: 'parse create',
- };
-
- item = {
- clear: null,
- interval: 15,
- disable: [],
- enable: 1,
- select: undefined,
- highlight: undefined,
- min: undefined,
- max: undefined,
- view: undefined,
- };
-
- key = {
- 40: 1, // Down
- 38: -1, // Up
- 39: 1, // Right
- 37: -1, // Left
- go(timeChange) {
- this.set(
- 'highlight',
- this.item.highlight.pick + timeChange * this.item.interval,
- { interval: timeChange * this.item.interval },
- );
- this.render();
- },
- };
-
- $node: any;
-
- constructor(picker, public settings) {
- const clock = this;
- const elementValue = picker.$node[0].value;
- const elementDataValue = picker.$node.data('value');
- const valueString = elementDataValue || elementValue;
- const formatString = elementDataValue ? settings.formatSubmit : settings.format;
- clock.$node = picker.$node;
- clock
- .set('min', settings.min)
- .set('max', settings.max)
- .set('select', valueString || '00:00', {
- format: formatString,
- });
-
- picker
- .on('render', () => {
- const $pickerHolder = picker.$root.children();
- const $viewset = $pickerHolder.find(`.${settings.klass.viewset}`);
- const vendors = function (prop) {
- return ['webkit', 'moz', 'ms', 'o', ''].map((vendor) => (vendor ? `-${vendor}-` : '') + prop);
- };
- const animations = function ($el, state) {
- vendors('transform').map((prop) => {
- $el.css(prop, state);
- });
- vendors('transition').map((prop) => {
- $el.css(prop, state);
- });
- };
- if ($viewset.length) {
- animations($pickerHolder, 'none');
- $pickerHolder[0].scrollTop = ~~$viewset.position().top - ($viewset[0].clientHeight * 2);
- animations($pickerHolder, '');
- }
- }, 1)
- .on('open', () => {
- picker.$root.find('button').attr('disabled', false);
- }, 1)
- .on('close', () => {
- picker.$root.find('button').attr('disabled', true);
- }, 1);
- }
-
- set(type, value?, options?) {
- const clock = this;
- const clockItem = clock.item;
-
- if (value === null) {
- if (type == 'clear') type = 'select';
- clockItem[type] = value;
- return clock;
- }
-
- // Otherwise go through the queue of methods, and invoke the functions.
- // Update this as the time unit, and set the final value as this item.
- // * In the case of `enable`, keep the queue but set `disable` instead.
- clockItem[type] = clock.queue[type].split(' ').map((method) => {
- value = clock[method](type, value, options);
- return value;
- }).pop();
-
- if (type == 'select') {
- clock.set('highlight', clockItem.select, options);
- } else if (type == 'highlight') {
- clock.set('view', clockItem.highlight, options);
- } else if (type == 'min') {
- clock.set('max', clockItem.max, options);
- }
- return clock;
- }
-
- get(type) {
- return this.item[type];
- }
-
- formats = {
- H(string, timeObject) {
- return string ? _.digits(string) : `${timeObject.hour % 24}`;
- },
- i(string, timeObject) {
- return string ? 2 : _.lead(timeObject.mins);
- },
-
- // Create an array by splitting the formatting string passed.
- toArray(formatString) { return formatString.split(/(h{1,2}|H{1,2}|i|a|A|!.)/g); },
- // Format an object into a string using the formatting options.
- toString(formatString, itemObject) {
- const clock = this;
- return clock.formats.toArray(formatString).map((label) => _.trigger(clock.formats[label], clock, [0, itemObject]) || label.replace(/^!/, '')).join('');
- },
- };
-
- create(type, value?, options?) {
- const clock = this;
- value = value === undefined ? type : value;
- if (_.isDate(value)) {
- value = [value.getHours(), value.getMinutes()];
- }
- if (value && typeof value === 'object' && _.isInteger(value.pick)) {
- value = value.pick;
- } else if (Array.isArray(value)) {
- value = +value[0] * MINUTES_IN_HOUR + (+value[1]);
- }
- if (type == 'max' && value < clock.item.min.pick) {
- value += MINUTES_IN_DAY;
- }
- if (type != 'min' && type != 'max' && (value - clock.item.min.pick) % clock.item.interval !== 0) {
- value += clock.item.interval;
- }
- value = clock.normalize(type, value, options);
- return {
- hour: ~~(HOURS_IN_DAY + value / MINUTES_IN_HOUR) % HOURS_IN_DAY,
- mins: (MINUTES_IN_HOUR + value % MINUTES_IN_HOUR) % MINUTES_IN_HOUR,
- time: (MINUTES_IN_DAY + value) % MINUTES_IN_DAY,
- pick: value % MINUTES_IN_DAY,
- };
- }
-
- normalize(type, value, _options) {
- const { interval } = this.item;
- const minTime = this.item.min && this.item.min.pick || 0;
- value -= type == 'min' ? 0 : (value - minTime) % interval;
- return value;
- }
-
- measure(type, value, options) {
- value ||= type == 'min' ? [0, 0] : [HOURS_IN_DAY - 1, MINUTES_IN_HOUR - 1];
- if (typeof value === 'string') {
- value = this.parse(type, value);
- } else if (value && typeof value === 'object' && _.isInteger(value.pick)) {
- value = this.normalize(type, value.pick, options);
- }
- return value;
- }
-
- scope = function (timeObject) {
- const minLimit = this.item.min.pick;
- const maxLimit = this.item.max.pick;
- return this.create(timeObject.pick > maxLimit ? maxLimit : timeObject.pick < minLimit ? minLimit : timeObject);
- }; // TimePicker.prototype.scope
-
- parse(type, value, options?) {
- let hour; let minutes; let item; let parseValue;
- const clock = this;
- const parsingObject = {};
- if (!value || typeof value !== 'string') return value;
-
- if (!(options && options.format)) {
- options = options || {};
- options.format = clock.settings.format;
- }
-
- clock.formats.toArray(options.format).map((label) => {
- let substring;
- const formattingLabel = clock.formats[label];
- const formatLength = formattingLabel
- ? _.trigger(formattingLabel, clock, [value, parsingObject])
- : label.replace(/^!/, '').length;
- if (formattingLabel) {
- substring = value.substr(0, formatLength);
- parsingObject[label] = substring.match(/^\d+$/) ? +substring : substring;
- }
- value = value.substr(formatLength);
- });
-
- // Grab the hour and minutes from the parsing object.
- for (item in parsingObject) {
- parseValue = parsingObject[item];
- if (_.isInteger(parseValue)) {
- if (item.match(/^(h|hh)$/i)) hour = parseValue;
- else if (item == 'i') minutes = parseValue;
- }
- }
-
- // Calculate it in minutes and return.
- return hour * MINUTES_IN_HOUR + minutes;
- }
-
- nodes(isOpen) {
- const clock = this;
- const { settings } = clock;
- const selectedObject = clock.item.select;
- const highlightedObject = clock.item.highlight;
- const viewsetObject = clock.item.view;
-
- return _.node(
- 'ul',
- _.group({
- min: clock.item.min.pick,
- max: clock.item.max.pick,
- i: clock.item.interval,
- node: 'li',
- item(loopedTime) {
- loopedTime = clock.create(loopedTime);
- const timeMinutes = loopedTime.pick;
- const isSelected = selectedObject && selectedObject.pick == timeMinutes;
- const isHighlighted = highlightedObject && highlightedObject.pick == timeMinutes;
- const formattedTime = _.trigger(clock.formats.toString, clock, [settings.format, loopedTime]);
- return [
- _.trigger(clock.formats.toString, clock, [_.trigger(settings.formatLabel, clock, [loopedTime]) || settings.format, loopedTime]),
- (function (klasses) {
- if (isSelected) klasses.push(settings.klass.selected);
-
- if (isHighlighted) klasses.push(settings.klass.highlighted);
-
- if (viewsetObject && viewsetObject.pick == timeMinutes) {
- klasses.push(settings.klass.viewset);
- }
-
- return klasses.join(' ');
- }([settings.klass.listItem])),
- `data-pick=${loopedTime.pick} ${_.ariaAttr({
- role: 'option',
- label: formattedTime,
- selected: isSelected && clock.$node.val() === formattedTime ? true : null,
- activedescendant: isHighlighted ? true : null,
- disabled: null,
- })}`,
- ];
- },
- })
- + _.node(
- 'li',
- _.node(
- 'button',
- settings.clear,
- settings.klass.buttonClear,
- `type=button data-clear=1${isOpen ? '' : ' disabled'} ${_.ariaAttr({ controls: clock.$node[0].id })}`,
- ),
- '',
- _.ariaAttr({ role: 'presentation' }),
- ),
- settings.klass.list,
- _.ariaAttr({ role: 'listbox', controls: clock.$node[0].id }),
- );
- }
-
- static defaults = (function (prefix) {
- return {
- clear: 'Clear',
- format: 'h:i A',
- interval: 30,
- closeOnSelect: true,
- closeOnClear: true,
- updateInput: true,
- klass: {
- picker: `${prefix} ${prefix}--time`,
- holder: `${prefix}__holder`,
- list: `${prefix}__list`,
- listItem: `${prefix}__list-item`,
- disabled: `${prefix}__list-item--disabled`,
- selected: `${prefix}__list-item--selected`,
- highlighted: `${prefix}__list-item--highlighted`,
- viewset: `${prefix}__list-item--viewset`,
- now: `${prefix}__list-item--now`,
- buttonClear: `${prefix}__button--clear`,
- },
- };
- }(Picker.klasses().picker));
-}
diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json
index 954d87213..ae3bc477d 100644
--- a/packages/ui-default/package.json
+++ b/packages/ui-default/package.json
@@ -34,7 +34,6 @@
"@types/katex": "^0.16.8",
"@types/markdown-it": "^14.1.2",
"@types/nunjucks": "^3.2.6",
- "@types/pickadate": "^3.5.35",
"@types/qrcode": "^1.5.6",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
@@ -81,7 +80,6 @@
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"p-queue": "^9.1.2",
- "pickadate": "patch:pickadate@npm%3A3.6.4#~/.yarn/patches/pickadate-npm-3.6.4-5795acb19d.patch",
"postcss-loader": "7.3.4",
"postcss-preset-mantine": "^1.18.0",
"prism-themes": "^1.9.0",
diff --git a/packages/ui-default/theme/default.js b/packages/ui-default/theme/default.js
index 6e5c17597..417db58c9 100644
--- a/packages/ui-default/theme/default.js
+++ b/packages/ui-default/theme/default.js
@@ -1,8 +1,5 @@
import 'normalize.css/normalize.css';
import 'allotment/dist/style.css';
-import 'pickadate/lib/themes/classic.css';
-import 'pickadate/lib/themes/classic.date.css';
-import 'pickadate/lib/themes/classic.time.css';
import 'katex/dist/katex.min.css';
import '@mantine/core/styles.css';
import '@mantine/notifications/styles.css';
@@ -26,7 +23,6 @@ import 'vj/misc/structure.styl';
import 'vj/misc/section.styl';
import 'vj/misc/nothing.styl';
import 'vj/components/editor/cmeditor.styl';
-import 'vj/components/datepicker/datepicker.styl';
import './dark.styl';
import './bp6-compat.css';