@vespercms/admin 0.1.0-next.0 → 0.1.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import {
8
8
  import { MultiCombobox, RadioGroup, Select, Switch, cx } from '@vespercms/ui';
9
9
  import { useState } from 'react';
10
10
  import type { WidgetProps } from '../types.ts';
11
+ import { useBlankWhenEmpty } from './blank-date.ts';
11
12
 
12
13
  /*
13
14
  * The simple built-in widgets (spec §6.3.2). Each follows the custom widget
@@ -148,11 +149,14 @@ export function DateWidget({
148
149
  invalid,
149
150
  disabled,
150
151
  }: WidgetProps<Date | string>) {
152
+ const text = dateText(value);
153
+ const blank = useBlankWhenEmpty(text);
151
154
  return (
152
155
  <input
153
156
  id={id}
154
157
  type="date"
155
- value={dateText(value)}
158
+ value={text}
159
+ {...blank}
156
160
  onChange={(event) => {
157
161
  const text = event.target.value;
158
162
  if (text === '')
@@ -0,0 +1,32 @@
1
+ import { useState, type FocusEvent, type KeyboardEvent } from 'react';
2
+
3
+ /*
4
+ * Safari draws today's date and time, greyed, in an empty date field, and
5
+ * editors took it for a time already chosen: "Comes down" looked set when it
6
+ * wasn't (WebKit bug 233215). An empty field is drawn blank until it's
7
+ * clicked or typed in, and blank again when it's left with nothing in it (studio.css
8
+ * hides `::-webkit-datetime-edit` under `data-blank`). Chrome's
9
+ * "mm/dd/yyyy" goes with it; the label says what goes there, and Firefox,
10
+ * which draws neither, is unchanged.
11
+ */
12
+
13
+ /** Keys that pass through or leave a field without starting to fill it in. */
14
+ const PASSING = new Set(['Tab', 'Shift', 'Escape', 'Enter', 'Meta', 'Control', 'Alt']);
15
+
16
+ /** Props for a `date` or `datetime-local` input whose value is `value`. */
17
+ export function useBlankWhenEmpty(value: string) {
18
+ const [engaged, setEngaged] = useState(false);
19
+ return {
20
+ 'data-blank': value === '' && !engaged ? '' : undefined,
21
+ onPointerDown: () => {
22
+ setEngaged(true);
23
+ },
24
+ onKeyDown: (event: KeyboardEvent) => {
25
+ if (!PASSING.has(event.key)) setEngaged(true);
26
+ },
27
+ // A date typed partway has no value yet, but its parts must stay in sight.
28
+ onBlur: (event: FocusEvent<HTMLInputElement>) => {
29
+ if (!event.currentTarget.validity.badInput) setEngaged(false);
30
+ },
31
+ };
32
+ }
@@ -14,6 +14,7 @@ import { useEditor } from '../editor/context.tsx';
14
14
  import { ChoosePageButton } from '../editor/PagePicker.tsx';
15
15
  import type { WidgetProps } from '../types.ts';
16
16
  import { inputClass } from './basic.tsx';
17
+ import { useBlankWhenEmpty } from './blank-date.ts';
17
18
 
18
19
  /*
19
20
  * The built-in widgets with behaviour of their own (spec §6.3.2). Every one
@@ -253,12 +254,15 @@ export function DateTimeWidget({
253
254
  value instanceof Date ? value : typeof value === 'string' && value ? new Date(value) : null;
254
255
  const valid = instant !== null && !Number.isNaN(instant.getTime());
255
256
  const name = zoneName(zone, valid ? instant : new Date());
257
+ const wall = valid ? wallTime(instant, zone) : '';
258
+ const blank = useBlankWhenEmpty(wall);
256
259
  return (
257
260
  <>
258
261
  <input
259
262
  id={id}
260
263
  type="datetime-local"
261
- value={valid ? wallTime(instant, zone) : ''}
264
+ value={wall}
265
+ {...blank}
262
266
  onChange={(event) => {
263
267
  if (event.target.value === '') {
264
268
  onChange(undefined);