@galaxy-io/dls 2.1.0 → 2.2.0

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.
@@ -1,7 +1,13 @@
1
- import { FontFamily } from "../theme/enums.js";
1
+ import VisuallyHidden from "../a11y/VisuallyHidden.js";
2
+ import { FontFamily, Orientation } from "../theme/enums.js";
2
3
  import Text, { TextSize, TextVariant } from "../text/Text.js";
3
4
  import { useControlledState } from "../hooks/useControlledState.js";
5
+ import { useControlledOpenState } from "../hooks/useControlledOpenState.js";
4
6
  import Flex, { AlignItems } from "../layout/Flex.js";
7
+ import Box from "../layout/Box.js";
8
+ import Divider from "../layout/Divider.js";
9
+ import Popover, { PopoverRole, PopoverTrigger, PopoverVariant } from "../overlays/Popover.js";
10
+ import Icon, { IconVariant } from "../icons/Icon.js";
5
11
  import Button, { ButtonSize, ButtonVariant } from "../buttons/Button.js";
6
12
  import ButtonGroup from "../buttons/ButtonGroup.js";
7
13
  import { useDevWarning } from "./internal/devWarn.js";
@@ -9,7 +15,10 @@ import { FieldContext } from "../hooks/useField.js";
9
15
  import { useFieldWiring } from "./internal/fieldWiring.js";
10
16
  import Field, { FieldSize } from "./Field.js";
11
17
  import { rootStyle, useOwnField } from "./internal/ownField.js";
18
+ import { BareFieldContext, useIsBareField } from "./internal/bareField.js";
19
+ import "./internal/innerButton.js";
12
20
  import { InputSize, InputVariant } from "./Input.js";
21
+ import { useDedupedOpen, useRestoreFocusAfterOutsidePress, useRetriedFocus } from "./internal/useComboboxPopover.js";
13
22
  import TextInput from "./TextInput.js";
14
23
  import SelectInput, { SelectInputSize, SelectInputVariant } from "./SelectInput.js";
15
24
  import ToggleInput, { ToggleInputSize, ToggleInputVariant } from "./ToggleInput.js";
@@ -18,35 +27,45 @@ import { styled } from "@linaria/react";
18
27
  import { forwardRef, useMemo, useRef, useState } from "react";
19
28
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
20
29
  import { cx } from "@linaria/core";
30
+ import { useMergeRefs } from "@floating-ui/react";
31
+ import { ClockIcon, XIcon } from "@phosphor-icons/react";
21
32
  import cronstrue from "cronstrue";
22
33
  //#region src/inputs/CronInput.tsx
23
34
  /**
24
35
  * The form control for a recurring schedule written as a five-field cron expression
25
- * (`minute hour day-of-month month day-of-week`). It shows a small builder: a frequency
26
- * (Hourly / Daily / Weekly / Monthly / Custom) and the fields that frequency needs (the minute, the
27
- * time, the weekdays, the day of the month), and writes the expression. Expressions the builder
28
- * cannot represent open in Custom, where the expression is edited as text. Under the fields a
29
- * sentence from `cronstrue` reads the schedule back ("Runs at 09:00, Monday through Friday.").
36
+ * (`minute hour day-of-month month day-of-week`). A field shows the expression (or a placeholder)
37
+ * and opens a schedule panel below it: the presets ("Every 15 minutes", "Weekdays at 09:00"), a
38
+ * builder (a frequency, Hourly / Daily / Weekly / Monthly / Custom, and the fields that frequency
39
+ * needs: the minute, the time, the weekdays, the day of the month), the expression itself as text,
40
+ * and a sentence from `cronstrue` that reads the schedule back ("Runs at 09:00, Monday through
41
+ * Friday."). Expressions the builder cannot represent show as Custom and are edited as text.
30
42
  *
31
- * The value is the expression string. CronInput never validates (the app passes `error`; the
32
- * sentence only says when it cannot read an expression), holds no time zone, and does not support
33
- * seconds or year fields. New in 2.0 (sources: rafa's CronInput and Filament's schedule fields).
43
+ * The field is a select-only combobox with a dialog popup (`Popover` `MANUAL`, `DIALOG`), like
44
+ * DateInput; CronInput moves focus to the expression field on open, so a schedule can be typed
45
+ * straight away. The value is the expression string; an empty string shows the placeholder.
46
+ *
47
+ * CronInput never validates (the app passes `error`; the sentence only says when it cannot read an
48
+ * expression), holds no time zone, and does not support seconds or year fields. Sources: rafa's
49
+ * CronInput and Filament's schedule fields.
50
+ *
51
+ * SSR: closed by default, so the server renders the field only. Nothing here touches `window` or
52
+ * `document` at module scope.
34
53
  */
35
- /** Canonical rungs; CronInput supports all five and passes the rung to every control it renders. */
54
+ /** Canonical rungs; CronInput supports all five (the Input rung table). */
36
55
  var CronInputSize = /* @__PURE__ */ function(CronInputSize) {
37
- /** 20px controls, caption text. */
56
+ /** 20px field, caption text, 12px icon. */
38
57
  CronInputSize["X_SMALL"] = "X_SMALL";
39
- /** 24px controls, body_sm text. */
58
+ /** 24px field, body_sm text, 12px icon. */
40
59
  CronInputSize["SMALL"] = "SMALL";
41
- /** 32px controls, body_md text. The default. */
60
+ /** 32px field, body_md text, 14px icon. The default. */
42
61
  CronInputSize["MEDIUM"] = "MEDIUM";
43
- /** 36px controls, body_lg text. */
62
+ /** 36px field, body_lg text, 14px icon. */
44
63
  CronInputSize["LARGE"] = "LARGE";
45
- /** 40px controls, body_lg text. */
64
+ /** 40px field, body_lg text, 16px icon. */
46
65
  CronInputSize["X_LARGE"] = "X_LARGE";
47
66
  return CronInputSize;
48
67
  }({});
49
- /** Resting fill of the builder's controls (Constitution V-A surface set). */
68
+ /** Resting fill of the field (Constitution V-A surface set). */
50
69
  var CronInputVariant = /* @__PURE__ */ function(CronInputVariant) {
51
70
  /** `background.base` fill. */
52
71
  CronInputVariant["BASE"] = "BASE";
@@ -60,8 +79,43 @@ var CronInputVariant = /* @__PURE__ */ function(CronInputVariant) {
60
79
  }({});
61
80
  /** The uncontrolled default: daily at 00:00. */
62
81
  var CRON_INPUT_DEFAULT_VALUE = "0 0 * * *";
63
- /** Width of the two-digit selects (minute, hour, day of month). */
64
- /** Width of the Custom expression field without `fillWidth`. */
82
+ /** The presets shown when the app passes none. */
83
+ var CRON_INPUT_DEFAULT_PRESETS = [
84
+ {
85
+ label: "Every 5 minutes",
86
+ value: "*/5 * * * *"
87
+ },
88
+ {
89
+ label: "Every 15 minutes",
90
+ value: "*/15 * * * *"
91
+ },
92
+ {
93
+ label: "Hourly",
94
+ value: "0 * * * *"
95
+ },
96
+ {
97
+ label: "Daily at midnight",
98
+ value: "0 0 * * *"
99
+ },
100
+ {
101
+ label: "Weekdays at 09:00",
102
+ value: "0 9 * * 1-5"
103
+ },
104
+ {
105
+ label: "Weekly on Monday",
106
+ value: "0 0 * * 1"
107
+ },
108
+ {
109
+ label: "Monthly on the 1st",
110
+ value: "0 0 1 * *"
111
+ }
112
+ ];
113
+ /** Field width without `fillWidth` (DateInput's single width). */
114
+ /** The presets column's narrowest width. */
115
+ /** Width of the two-digit selects (minute, hour, day of month), px for `Box width` (DateInput's time selects). */
116
+ var CRON_INPUT_SELECT_WIDTH = 64;
117
+ /** Frames to wait for the panel's portal before giving up on moving focus into it. */
118
+ var CRON_INPUT_FOCUS_RETRY_FRAMES = 10;
65
119
  /** The neutral hint shown when `cronstrue` cannot read the expression. */
66
120
  var CRON_INPUT_HINT = "Enter five fields: minute, hour, day of month, month, day of week.";
67
121
  /** Each builder row's label; the label column is as wide as the longest, whichever rows show. */
@@ -246,7 +300,11 @@ function builderToValue(frequency, parts) {
246
300
  case "MONTHLY": return `${minute} ${hour} ${dayOfMonth} * *`;
247
301
  }
248
302
  }
249
- /** The sentence under the builder, or `null` when `cronstrue` cannot read the expression. */
303
+ /** An expression with its fields single-spaced, for comparing against a preset. */
304
+ function normalize(expression) {
305
+ return expression.trim().split(/\s+/).join(" ");
306
+ }
307
+ /** The sentence for an expression, or `null` when `cronstrue` cannot read it. */
250
308
  function describe(expression) {
251
309
  if (expression.split(/\s+/).length !== 5) return null;
252
310
  try {
@@ -262,34 +320,32 @@ function describe(expression) {
262
320
  return null;
263
321
  }
264
322
  }
265
- var CRON_INPUT_SIZE_TO_TOGGLE_INPUT_SIZE_MAP = {
266
- ["X_SMALL"]: ToggleInputSize.X_SMALL,
267
- ["SMALL"]: ToggleInputSize.SMALL,
268
- ["MEDIUM"]: ToggleInputSize.MEDIUM,
269
- ["LARGE"]: ToggleInputSize.LARGE,
270
- ["X_LARGE"]: ToggleInputSize.X_LARGE
271
- };
272
- var CRON_INPUT_SIZE_TO_SELECT_INPUT_SIZE_MAP = {
273
- ["X_SMALL"]: SelectInputSize.X_SMALL,
274
- ["SMALL"]: SelectInputSize.SMALL,
275
- ["MEDIUM"]: SelectInputSize.MEDIUM,
276
- ["LARGE"]: SelectInputSize.LARGE,
277
- ["X_LARGE"]: SelectInputSize.X_LARGE
278
- };
279
- var CRON_INPUT_SIZE_TO_INPUT_SIZE_MAP = {
280
- ["X_SMALL"]: InputSize.X_SMALL,
281
- ["SMALL"]: InputSize.SMALL,
282
- ["MEDIUM"]: InputSize.MEDIUM,
283
- ["LARGE"]: InputSize.LARGE,
284
- ["X_LARGE"]: InputSize.X_LARGE
323
+ /** Field icon px per rung: one step below the rung's III-A.4 icon (DateInput's). */
324
+ var CRON_INPUT_SIZE_TO_ICON_PX_MAP = {
325
+ ["X_SMALL"]: 12,
326
+ ["SMALL"]: 12,
327
+ ["MEDIUM"]: 14,
328
+ ["LARGE"]: 14,
329
+ ["X_LARGE"]: 16
285
330
  };
331
+ /** The clear button's rung: 20px up to MEDIUM, 24px for LARGE and X_LARGE (Input's). */
286
332
  var CRON_INPUT_SIZE_TO_BUTTON_SIZE_MAP = {
287
333
  ["X_SMALL"]: ButtonSize.X_SMALL,
288
- ["SMALL"]: ButtonSize.SMALL,
289
- ["MEDIUM"]: ButtonSize.MEDIUM,
290
- ["LARGE"]: ButtonSize.LARGE,
291
- ["X_LARGE"]: ButtonSize.X_LARGE
334
+ ["SMALL"]: ButtonSize.X_SMALL,
335
+ ["MEDIUM"]: ButtonSize.X_SMALL,
336
+ ["LARGE"]: ButtonSize.SMALL,
337
+ ["X_LARGE"]: ButtonSize.SMALL
292
338
  };
339
+ /** The clear button's px per rung, for the reserved padding (16 on X_SMALL: `X_SMALL_INNER_BUTTON_CSS`). */
340
+ /** Field text step per rung (the canonical control text). */
341
+ var CRON_INPUT_SIZE_TO_TEXT_SIZE_MAP = {
342
+ ["X_SMALL"]: TextSize.CAPTION,
343
+ ["SMALL"]: TextSize.BODY_SM,
344
+ ["MEDIUM"]: TextSize.BODY_MD,
345
+ ["LARGE"]: TextSize.BODY_LG,
346
+ ["X_LARGE"]: TextSize.BODY_LG
347
+ };
348
+ /** The `Field` a CronInput renders for its own `label` / `error` uses the same rung. */
293
349
  var CRON_INPUT_SIZE_TO_FIELD_SIZE_MAP = {
294
350
  ["X_SMALL"]: FieldSize.X_SMALL,
295
351
  ["SMALL"]: FieldSize.SMALL,
@@ -297,53 +353,161 @@ var CRON_INPUT_SIZE_TO_FIELD_SIZE_MAP = {
297
353
  ["LARGE"]: FieldSize.LARGE,
298
354
  ["X_LARGE"]: FieldSize.X_LARGE
299
355
  };
300
- /** Row labels use the rung's control text. */
301
- var CRON_INPUT_SIZE_TO_TEXT_SIZE_MAP = {
302
- ["X_SMALL"]: TextSize.CAPTION,
303
- ["SMALL"]: TextSize.BODY_SM,
304
- ["MEDIUM"]: TextSize.BODY_MD,
305
- ["LARGE"]: TextSize.BODY_LG,
306
- ["X_LARGE"]: TextSize.BODY_LG
356
+ /** Panel fill: SelectInput's map. A ghost field (`isGhost`) gets a readable `BASE` panel instead. */
357
+ var CRON_INPUT_VARIANT_TO_POPOVER_VARIANT_MAP = {
358
+ ["BASE"]: PopoverVariant.PRIMARY,
359
+ ["PRIMARY"]: PopoverVariant.PRIMARY,
360
+ ["SECONDARY"]: PopoverVariant.SECONDARY,
361
+ ["TERTIARY"]: PopoverVariant.TERTIARY
307
362
  };
308
- /** The preview uses Field's help steps. */
309
- var CRON_INPUT_SIZE_TO_HELP_TEXT_SIZE_MAP = {
310
- ["X_SMALL"]: TextSize.CAPTION,
311
- ["SMALL"]: TextSize.CAPTION,
312
- ["MEDIUM"]: TextSize.BODY_SM,
313
- ["LARGE"]: TextSize.BODY_SM,
314
- ["X_LARGE"]: TextSize.BODY_MD
363
+ /** Holds the field box, the trailing parts and the hidden summary. */
364
+ var Wrapper = /*#__PURE__*/ styled("div")({
365
+ name: "Wrapper",
366
+ class: "gx-CronInput-Wrapper-wmu78vg",
367
+ propsAsIs: false
368
+ });
369
+ /** Without `fillWidth`: 240px. */
370
+ var WIDTH_CSS = "gx-CronInput-WIDTH_CSS-wvetpyb";
371
+ /**
372
+ * The combobox (focused element and Popover's reference). No outline; focus is `FOCUS_CSS`.
373
+ * Declared before the frame maps.
374
+ */
375
+ var Control = /*#__PURE__*/ styled("div")({
376
+ name: "Control",
377
+ class: "gx-CronInput-Control-cwvebjn",
378
+ propsAsIs: false
379
+ });
380
+ /** Rest look per variant: fill and hairline color. */
381
+ var CRON_INPUT_VARIANT_TO_CSS_MAP = {
382
+ ["BASE"]: "gx-CronInput-BASE-bendsmc",
383
+ ["PRIMARY"]: "gx-CronInput-PRIMARY-p1iubrvv",
384
+ ["SECONDARY"]: "gx-CronInput-SECONDARY-s1i5sgim",
385
+ ["TERTIARY"]: "gx-CronInput-TERTIARY-t1xjp6qf"
315
386
  };
316
- var CRON_INPUT_VARIANT_TO_TOGGLE_INPUT_VARIANT_MAP = {
317
- ["BASE"]: ToggleInputVariant.BASE,
318
- ["PRIMARY"]: ToggleInputVariant.PRIMARY,
319
- ["SECONDARY"]: ToggleInputVariant.SECONDARY,
320
- ["TERTIARY"]: ToggleInputVariant.TERTIARY
387
+ /** `isGhost`: no fill and no hairline at rest. Declared after the variant map, which it replaces. */
388
+ var GHOST_CSS = "gx-CronInput-GHOST_CSS-g73e2ix";
389
+ /** Hover on an editable field: `border.hovered` (stronger than `border.primary`, Constitution III.6). */
390
+ var CRON_INPUT_VARIANT_TO_HOVER_CSS_MAP = {
391
+ ["BASE"]: "gx-CronInput-BASE-b1tyf0oc",
392
+ ["PRIMARY"]: "gx-CronInput-PRIMARY-ppl1i3v",
393
+ ["SECONDARY"]: "gx-CronInput-SECONDARY-s1r1kpa2",
394
+ ["TERTIARY"]: "gx-CronInput-TERTIARY-tr1ns5a"
321
395
  };
322
- var CRON_INPUT_VARIANT_TO_SELECT_INPUT_VARIANT_MAP = {
323
- ["BASE"]: SelectInputVariant.BASE,
324
- ["PRIMARY"]: SelectInputVariant.PRIMARY,
325
- ["SECONDARY"]: SelectInputVariant.SECONDARY,
326
- ["TERTIARY"]: SelectInputVariant.TERTIARY
396
+ /** Hover on an editable ghost field: the frame appears, `border.hovered` with no fill. */
397
+ var GHOST_HOVER_CSS = "gx-CronInput-GHOST_HOVER_CSS-g1279sor";
398
+ /**
399
+ * Height, start inset, gap and the end inset that reserves the icon zone (rung padding + icon +
400
+ * gap) per rung (SelectInput's table). The start inset subtracts the 0.5px hairline so the value
401
+ * starts on a whole pixel.
402
+ */
403
+ var CRON_INPUT_SIZE_TO_CSS_MAP = {
404
+ ["X_SMALL"]: "gx-CronInput-X_SMALL-x21adkg",
405
+ ["SMALL"]: "gx-CronInput-SMALL-snda18n",
406
+ ["MEDIUM"]: "gx-CronInput-MEDIUM-m1wqniqa",
407
+ ["LARGE"]: "gx-CronInput-LARGE-l144xlp2",
408
+ ["X_LARGE"]: "gx-CronInput-X_LARGE-xynnhma"
327
409
  };
328
- var CRON_INPUT_VARIANT_TO_INPUT_VARIANT_MAP = {
329
- ["BASE"]: InputVariant.BASE,
330
- ["PRIMARY"]: InputVariant.PRIMARY,
331
- ["SECONDARY"]: InputVariant.SECONDARY,
332
- ["TERTIARY"]: InputVariant.TERTIARY
410
+ /** With `isClearable`: the end inset also reserves the clear button and a gap. */
411
+ var CRON_INPUT_SIZE_TO_CLEARABLE_CSS_MAP = {
412
+ ["X_SMALL"]: "gx-CronInput-X_SMALL-x11zmzgg",
413
+ ["SMALL"]: "gx-CronInput-SMALL-sgvaglw",
414
+ ["MEDIUM"]: "gx-CronInput-MEDIUM-mv01uf1",
415
+ ["LARGE"]: "gx-CronInput-LARGE-lkzx5g9",
416
+ ["X_LARGE"]: "gx-CronInput-X_LARGE-xvvvzs"
333
417
  };
334
- /** The group: the builder rows, then the preview sentence. */
335
- var Group = /*#__PURE__*/ styled("div")({
336
- name: "Group",
337
- class: "gx-CronInput-Group-gmu78vg",
418
+ /**
419
+ * Focus, from the mouse or the keyboard: `FIELD_FOCUS`, `border.focused` at 1px inside the frame. `:focus:hover`
420
+ * beats the hover map, so a ghost field keeps it. Not applied while disabled (the disabled
421
+ * combobox is a `tabIndex={-1}` div, which a click can still focus).
422
+ */
423
+ var FOCUS_CSS = "gx-CronInput-FOCUS_CSS-f1067muk";
424
+ /** Open: `border.focused`. */
425
+ var OPEN_CSS = "gx-CronInput-OPEN_CSS-o54xey5";
426
+ /** Error border at rest, on hover, with focus and while open. */
427
+ var ERROR_CSS = "gx-CronInput-ERROR_CSS-e1hmaicf";
428
+ /** Disabled keeps the resting hairline; the text, the icon and the cursor say disabled. */
429
+ var DISABLED_CSS = "gx-CronInput-DISABLED_CSS-d66p23w";
430
+ /** Read-only: no pointer affordance. */
431
+ var READ_ONLY_CSS = "gx-CronInput-READ_ONLY_CSS-r1vuh2ii";
432
+ /** Stretch to the parent's width (wrapper, and the `Field` wrapper when there is one). */
433
+ var FILL_WIDTH_CSS = "gx-CronInput-FILL_WIDTH_CSS-fpavuga";
434
+ /** The expression (or placeholder): takes the row, truncates. */
435
+ var ValueDisplay = /*#__PURE__*/ styled("span")({
436
+ name: "ValueDisplay",
437
+ class: "gx-CronInput-ValueDisplay-v18d3ls7",
338
438
  propsAsIs: false
339
439
  });
440
+ /** The clock icon and the clear button, laid over the field's end. */
441
+ var Trailing = /*#__PURE__*/ styled("span")({
442
+ name: "Trailing",
443
+ class: "gx-CronInput-Trailing-t5fn29j",
444
+ propsAsIs: false
445
+ });
446
+ /** Trailing inset (the rung padding) and gap per rung. */
447
+ var CRON_INPUT_SIZE_TO_TRAILING_CSS_MAP = {
448
+ ["X_SMALL"]: "gx-CronInput-X_SMALL-x1f2acxv",
449
+ ["SMALL"]: "gx-CronInput-SMALL-srskmwt",
450
+ ["MEDIUM"]: "gx-CronInput-MEDIUM-m1p8cnb2",
451
+ ["LARGE"]: "gx-CronInput-LARGE-lnz42gb",
452
+ ["X_LARGE"]: "gx-CronInput-X_LARGE-x14lh8y9"
453
+ };
454
+ /** Wraps the clear button: the only part of `Trailing` that takes the pointer. */
455
+ var ClearSlot = /*#__PURE__*/ styled("span")({
456
+ name: "ClearSlot",
457
+ class: "gx-CronInput-ClearSlot-cnjxgnh",
458
+ propsAsIs: false
459
+ });
460
+ /** Hidden clear button: keeps its width, leaves the tab order and the accessibility tree. */
461
+ var CLEAR_HIDDEN_CSS = "gx-CronInput-CLEAR_HIDDEN_CSS-c1d4mcpq";
462
+ /** The panel content: the main row, then the summary footer. */
463
+ var PanelBody = /*#__PURE__*/ styled("div")({
464
+ name: "PanelBody",
465
+ class: "gx-CronInput-PanelBody-p11btr9k",
466
+ propsAsIs: false
467
+ });
468
+ /** The main row: presets, then the builder, 12px in and 12px apart. */
469
+ var PanelMain = /*#__PURE__*/ styled("div")({
470
+ name: "PanelMain",
471
+ class: "gx-CronInput-PanelMain-p1az6570",
472
+ propsAsIs: false
473
+ });
474
+ /**
475
+ * The summary footer: the schedule read back, set apart from the controls that make it. A hairline
476
+ * above and a faint foreground tint, so it reads as the panel's output on every panel fill. It
477
+ * wraps to the panel's width and never widens it.
478
+ */
479
+ var Summary = /*#__PURE__*/ styled("div")({
480
+ name: "Summary",
481
+ class: "gx-CronInput-Summary-s1phzlk6",
482
+ propsAsIs: false
483
+ });
484
+ /** Centers the 12px icon on the summary's first 16px line. */
485
+ var SummaryIcon = /*#__PURE__*/ styled("span")({
486
+ name: "SummaryIcon",
487
+ class: "gx-CronInput-SummaryIcon-su1hduy",
488
+ propsAsIs: false
489
+ });
490
+ /** The presets column. */
491
+ var PresetList = /*#__PURE__*/ styled("div")({
492
+ name: "PresetList",
493
+ class: "gx-CronInput-PresetList-pryjels",
494
+ propsAsIs: false
495
+ });
496
+ /** One preset. Declared before its state classes. */
497
+ var PresetButton = /*#__PURE__*/ styled("button")({
498
+ name: "PresetButton",
499
+ class: "gx-CronInput-PresetButton-pm47nq4",
500
+ propsAsIs: false
501
+ });
502
+ /** The preset whose value is the current expression. */
503
+ var SELECTED_PRESET_CSS = "gx-CronInput-SELECTED_PRESET_CSS-safotm8";
340
504
  /**
341
505
  * Row labels in the first column, controls in the second. Controls keep their own width
342
- * (`justify-items: start`), so the wide weekday row never stretches the frequency toggle.
506
+ * (`justify-items: start`), so the weekday row never stretches the frequency toggle.
343
507
  */
344
508
  var Rows = /*#__PURE__*/ styled("div")({
345
509
  name: "Rows",
346
- class: "gx-CronInput-Rows-rvetpyb",
510
+ class: "gx-CronInput-Rows-r1d29pk4",
347
511
  propsAsIs: false
348
512
  });
349
513
  /**
@@ -352,50 +516,59 @@ var Rows = /*#__PURE__*/ styled("div")({
352
516
  */
353
517
  var LabelStack = /*#__PURE__*/ styled("span")({
354
518
  name: "LabelStack",
355
- class: "gx-CronInput-LabelStack-lwvebjn",
519
+ class: "gx-CronInput-LabelStack-l1tt5fyf",
356
520
  propsAsIs: false
357
521
  });
358
522
  /** A row label that only sizes the label column: not painted, not announced. */
359
- var LABEL_SIZER_CSS = "gx-CronInput-LABEL_SIZER_CSS-lendsmc";
360
- /** Holds a two-digit select at its fixed width. */
361
- var SelectSlot = /*#__PURE__*/ styled("span")({
362
- name: "SelectSlot",
363
- class: "gx-CronInput-SelectSlot-s1iubrvv",
523
+ var LABEL_SIZER_CSS = "gx-CronInput-LABEL_SIZER_CSS-l10uqwad";
524
+ /** A full-width part of the rows grid (the divider above the expression). */
525
+ var RowSpan = /*#__PURE__*/ styled("div")({
526
+ name: "RowSpan",
527
+ class: "gx-CronInput-RowSpan-r1231567",
364
528
  propsAsIs: false
365
529
  });
366
- /** Holds the Custom expression field at its fixed width. */
367
- var ExpressionSlot = /*#__PURE__*/ styled("span")({
530
+ /** Stretches the expression field across the control column. */
531
+ var ExpressionSlot = /*#__PURE__*/ styled("div")({
368
532
  name: "ExpressionSlot",
369
- class: "gx-CronInput-ExpressionSlot-e1i5sgim",
533
+ class: "gx-CronInput-ExpressionSlot-e13n44nx",
370
534
  propsAsIs: false
371
535
  });
372
- /** Stretch to the parent's width (group, and the `Field` wrapper when there is one). */
373
- var FILL_WIDTH_CSS = "gx-CronInput-FILL_WIDTH_CSS-f1xjp6qf";
374
- /** With `fillWidth`, the control column takes the rest of each row. */
375
- var ROWS_FILL_CSS = "gx-CronInput-ROWS_FILL_CSS-r73e2ix";
376
- /** With `fillWidth`, the Custom field fills its cell. */
377
- var EXPRESSION_FILL_CSS = "gx-CronInput-EXPRESSION_FILL_CSS-e1tyf0oc";
378
- /** The builder and its preview, wired to the nearest Field (the CronInput's own or the app's). */
379
- function CronGroup({ cronProps, forwardedRef, isRoot }) {
380
- const { value, defaultValue = CRON_INPUT_DEFAULT_VALUE, onChange, size = "MEDIUM", variant = "PRIMARY", isGhost = false, label: _label, error, isError = false, isDisabled = false, isReadOnly = false, isRequired = false, fillWidth = false, ariaLabel, id, "aria-describedby": ariaDescribedBy, className, style, ...rest } = cronProps;
536
+ /** The field, the trailing parts and the schedule panel, wired to the nearest Field. */
537
+ function CronInputFrame({ cronProps, forwardedRef, isRoot }) {
538
+ const { value, defaultValue = CRON_INPUT_DEFAULT_VALUE, onChange, presets = CRON_INPUT_DEFAULT_PRESETS, size = "MEDIUM", variant = "PRIMARY", isGhost = false, label: _label, placeholder = "Select schedule…", error, isError = false, isClearable = false, isDisabled = false, isReadOnly = false, isRequired = false, fillWidth = false, ariaLabel, isOpen, defaultIsOpen = false, onOpenChange, id, "aria-describedby": ariaDescribedBy, className, style, ...rest } = cronProps;
539
+ const isBare = useIsBareField();
381
540
  const { field, controlId, describedBy, isInvalid, isRequired: isRequiredState, hasName } = useFieldWiring({
382
541
  id,
383
542
  ariaLabel,
384
543
  ariaLabelAttribute: rest["aria-label"],
385
544
  ariaLabelledBy: rest["aria-labelledby"],
386
545
  ariaDescribedBy,
387
- describedByIds: (groupId) => [`${groupId}-preview`],
546
+ describedByIds: (fieldId) => [`${fieldId}-summary`],
388
547
  error,
389
548
  isError,
390
549
  isRequired
391
550
  });
392
- const previewId = `${controlId}-preview`;
551
+ const panelId = `${controlId}-schedule`;
552
+ const summaryId = `${controlId}-summary`;
553
+ const expressionId = `${controlId}-expression-input`;
393
554
  const rowLabelId = (row) => `${controlId}-${row}`;
555
+ const controlRef = useRef(null);
556
+ const mergedRef = useMergeRefs([controlRef, forwardedRef]);
557
+ const panelRef = useRef(null);
558
+ const wrapperRef = useRef(null);
559
+ const isEditable = !isDisabled && !isReadOnly;
394
560
  const [expression, setExpression] = useControlledState({
395
561
  value,
396
562
  defaultValue,
397
563
  onChange
398
564
  });
565
+ const { isOpen: isOpenState, setOpen: setOpenState } = useControlledOpenState({
566
+ isOpen,
567
+ defaultIsOpen,
568
+ onOpenChange
569
+ });
570
+ const isShown = isOpenState && isEditable;
571
+ const { setOpen, isShownRef } = useDedupedOpen(isShown, setOpenState);
399
572
  const lastEmittedRef = useRef(null);
400
573
  const [builder, setBuilder] = useState(() => valueToBuilder(expression));
401
574
  const [lastSeen, setLastSeen] = useState(expression);
@@ -403,12 +576,31 @@ function CronGroup({ cronProps, forwardedRef, isRoot }) {
403
576
  setLastSeen(expression);
404
577
  if (expression !== lastEmittedRef.current) setBuilder((previous) => valueToBuilder(expression, previous.parts));
405
578
  }
579
+ /** Writes an expression the builder made. */
406
580
  const emit = (next) => {
407
581
  if (next === expression) return;
408
582
  lastEmittedRef.current = next;
409
583
  setExpression(next);
410
584
  };
411
- const isEditable = !isDisabled && !isReadOnly;
585
+ /** Writes an expression from outside the builder (typed, a preset, cleared). */
586
+ const commit = (next) => {
587
+ lastEmittedRef.current = null;
588
+ setExpression(next);
589
+ };
590
+ const shouldFocusExpressionRef = useRef(false);
591
+ const [lastShown, setLastShown] = useState(isShown);
592
+ if (lastShown !== isShown) {
593
+ setLastShown(isShown);
594
+ if (isShown) shouldFocusExpressionRef.current = true;
595
+ }
596
+ useRetriedFocus(shouldFocusExpressionRef, isShown, () => controlRef.current?.ownerDocument.getElementById(expressionId) ?? null, CRON_INPUT_FOCUS_RETRY_FRAMES);
597
+ const close = () => setOpen(false);
598
+ useRestoreFocusAfterOutsidePress({
599
+ isShown,
600
+ focusRef: controlRef,
601
+ insideRefs: [wrapperRef, panelRef],
602
+ focusScopeRefs: [panelRef]
603
+ });
412
604
  const { frequency, parts } = builder;
413
605
  /** Applies new parts in the current builder frequency and writes the expression. */
414
606
  const updateParts = (next) => {
@@ -437,46 +629,65 @@ function CronGroup({ cronProps, forwardedRef, isRoot }) {
437
629
  if (isOn && parts.weekdays.length === 1) return;
438
630
  updateParts({ weekdays: isOn ? parts.weekdays.filter((selected) => selected !== day) : [...parts.weekdays, day].sort((a, b) => a - b) });
439
631
  };
632
+ /** The field's keys: Enter, Space, ArrowDown and Alt+ArrowDown open the panel. */
633
+ const handleControlKeyDown = (event) => {
634
+ if (event.defaultPrevented || !isEditable || isShown) return;
635
+ if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
636
+ event.preventDefault();
637
+ setOpen(true);
638
+ }
639
+ };
640
+ const handleControlClick = () => {
641
+ if (!isEditable) return;
642
+ setOpen(!isShown);
643
+ };
644
+ /** Focus leaving both the field and the panel for another element closes the panel. */
645
+ const handleFocusOut = (event) => {
646
+ if (!isShownRef.current) return;
647
+ const next = event.relatedTarget;
648
+ if (next === null) return;
649
+ if (panelRef.current?.contains(next) || controlRef.current?.contains(next)) return;
650
+ close();
651
+ };
652
+ const clear = () => {
653
+ commit("");
654
+ if (isShown) close();
655
+ controlRef.current?.focus();
656
+ };
440
657
  const trimmed = expression.trim();
441
658
  const sentence = useMemo(() => describe(trimmed), [trimmed]);
442
- useDevWarning(!hasName, "[@galaxy-io/dls] CronInput has no accessible name. Pass `label`, wrap it in a labelled `Field`, or set `ariaLabel`.");
659
+ const hasValue = trimmed !== "";
660
+ useDevWarning(!hasName, "[@galaxy-io/dls] CronInput has no accessible name. Pass `label`, wrap it in a labelled `Field`, or set `ariaLabel`. `placeholder` is not a name.");
443
661
  const textSize = CRON_INPUT_SIZE_TO_TEXT_SIZE_MAP[size];
444
- const helpSize = CRON_INPUT_SIZE_TO_HELP_TEXT_SIZE_MAP[size];
445
- const selectSize = CRON_INPUT_SIZE_TO_SELECT_INPUT_SIZE_MAP[size];
446
- const selectVariant = CRON_INPUT_VARIANT_TO_SELECT_INPUT_VARIANT_MAP[variant];
447
- const controlState = {
448
- isDisabled,
449
- isReadOnly,
450
- isError: isInvalid,
451
- isRequired: isRequiredState
452
- };
662
+ const isClearHidden = !hasValue || !isEditable;
453
663
  const rowLabel = (row) => /* @__PURE__ */ jsx(Text, {
454
664
  as: "span",
455
665
  id: rowLabelId(row),
456
- size: textSize,
457
- variant: isDisabled ? TextVariant.DISABLED : TextVariant.SECONDARY,
666
+ size: TextSize.BODY_SM,
667
+ variant: TextVariant.SECONDARY,
458
668
  children: ROW_LABELS[row]
459
669
  });
460
670
  /** A two-digit select named by its row label (plus its own name inside a time row). */
461
671
  const numberSelect = (row, part, options, ownName) => {
462
672
  const selectId = `${rowLabelId(row)}-${part}`;
463
- return /* @__PURE__ */ jsx(SelectSlot, { children: /* @__PURE__ */ jsx(SelectInput, {
464
- id: selectId,
465
- ...ownName === void 0 ? { "aria-labelledby": rowLabelId(row) } : {
466
- ariaLabel: ownName,
467
- "aria-labelledby": `${rowLabelId(row)} ${selectId}`
468
- },
469
- options,
470
- value: String(parts[part]),
471
- onChange: (next) => {
472
- if (next !== null) updateParts({ [part]: Number(next) });
473
- },
474
- size: selectSize,
475
- variant: selectVariant,
476
- isGhost,
477
- fillWidth: true,
478
- ...controlState
479
- }) });
673
+ return /* @__PURE__ */ jsx(Box, {
674
+ width: CRON_INPUT_SELECT_WIDTH,
675
+ children: /* @__PURE__ */ jsx(SelectInput, {
676
+ id: selectId,
677
+ ...ownName === void 0 ? { "aria-labelledby": rowLabelId(row) } : {
678
+ ariaLabel: ownName,
679
+ "aria-labelledby": `${rowLabelId(row)} ${selectId}`
680
+ },
681
+ options,
682
+ value: String(parts[part]),
683
+ onChange: (next) => {
684
+ if (next !== null) updateParts({ [part]: Number(next) });
685
+ },
686
+ size: SelectInputSize.SMALL,
687
+ variant: SelectInputVariant.SECONDARY,
688
+ fillWidth: true
689
+ })
690
+ });
480
691
  };
481
692
  const timeRow = /* @__PURE__ */ jsxs(Fragment$1, { children: [rowLabel("time"), /* @__PURE__ */ jsxs(Flex, {
482
693
  alignItems: AlignItems.CENTER,
@@ -485,7 +696,7 @@ function CronGroup({ cronProps, forwardedRef, isRoot }) {
485
696
  numberSelect("time", "hour", HOUR_OPTIONS, "Hour"),
486
697
  /* @__PURE__ */ jsx(Text, {
487
698
  as: "span",
488
- size: textSize,
699
+ size: TextSize.BODY_SM,
489
700
  variant: TextVariant.TERTIARY,
490
701
  "aria-hidden": true,
491
702
  children: ":"
@@ -493,98 +704,179 @@ function CronGroup({ cronProps, forwardedRef, isRoot }) {
493
704
  numberSelect("time", "minute", MINUTE_OPTIONS, "Minute")
494
705
  ]
495
706
  })] });
496
- return /* @__PURE__ */ jsxs(Group, {
497
- ref: forwardedRef,
498
- role: "group",
499
- id: controlId,
500
- "aria-labelledby": field?.labelId,
501
- "aria-label": ariaLabel,
502
- "aria-describedby": describedBy,
503
- ...rest,
504
- className: cx(fillWidth && FILL_WIDTH_CSS, isRoot ? className : void 0),
707
+ const hasPresets = presets.length > 0;
708
+ const normalized = normalize(expression);
709
+ const body = /* @__PURE__ */ jsx(FieldContext.Provider, {
710
+ value: null,
711
+ children: /* @__PURE__ */ jsxs(PanelBody, {
712
+ onBlur: handleFocusOut,
713
+ children: [/* @__PURE__ */ jsxs(PanelMain, { children: [hasPresets && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PresetList, {
714
+ role: "group",
715
+ "aria-label": "Presets",
716
+ children: presets.map((preset) => {
717
+ const isPresetSelected = normalize(preset.value) === normalized;
718
+ return /* @__PURE__ */ jsx(PresetButton, {
719
+ type: "button",
720
+ "aria-pressed": isPresetSelected,
721
+ onClick: () => {
722
+ commit(preset.value);
723
+ close();
724
+ },
725
+ className: cx(isPresetSelected && SELECTED_PRESET_CSS),
726
+ children: /* @__PURE__ */ jsx(Text, {
727
+ as: "span",
728
+ size: TextSize.BODY_SM,
729
+ variant: isPresetSelected ? TextVariant.PRIMARY : TextVariant.SECONDARY,
730
+ children: preset.label
731
+ })
732
+ }, preset.label);
733
+ })
734
+ }), /* @__PURE__ */ jsx(Divider, { orientation: Orientation.VERTICAL })] }), /* @__PURE__ */ jsxs(Rows, { children: [
735
+ /* @__PURE__ */ jsxs(LabelStack, { children: [rowLabel("frequency"), Object.entries(ROW_LABELS).map(([row, text]) => row === "frequency" ? null : /* @__PURE__ */ jsx(Text, {
736
+ as: "span",
737
+ size: TextSize.BODY_SM,
738
+ className: LABEL_SIZER_CSS,
739
+ "aria-hidden": true,
740
+ children: text
741
+ }, row))] }),
742
+ /* @__PURE__ */ jsx(ToggleInput, {
743
+ options: FREQUENCY_OPTIONS,
744
+ value: frequency,
745
+ onChange: changeFrequency,
746
+ size: ToggleInputSize.SMALL,
747
+ variant: ToggleInputVariant.SECONDARY,
748
+ "aria-labelledby": rowLabelId("frequency")
749
+ }),
750
+ frequency === "HOURLY" && /* @__PURE__ */ jsxs(Fragment$1, { children: [rowLabel("minute"), numberSelect("minute", "minute", MINUTE_OPTIONS)] }),
751
+ frequency === "DAILY" && timeRow,
752
+ frequency === "WEEKLY" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
753
+ rowLabel("weekdays"),
754
+ /* @__PURE__ */ jsx(ButtonGroup, {
755
+ isAttached: true,
756
+ "aria-labelledby": rowLabelId("weekdays"),
757
+ children: WEEKDAYS.map(({ day, label, name }) => /* @__PURE__ */ jsx(Button, {
758
+ variant: ButtonVariant.SECONDARY,
759
+ size: ButtonSize.SMALL,
760
+ label,
761
+ ariaLabel: name,
762
+ isActive: parts.weekdays.includes(day),
763
+ onClick: () => toggleWeekday(day)
764
+ }, day))
765
+ }),
766
+ timeRow
767
+ ] }),
768
+ frequency === "MONTHLY" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
769
+ rowLabel("day"),
770
+ numberSelect("day", "dayOfMonth", DAY_OF_MONTH_OPTIONS),
771
+ timeRow
772
+ ] }),
773
+ /* @__PURE__ */ jsx(RowSpan, { children: /* @__PURE__ */ jsx(Divider, {}) }),
774
+ rowLabel("expression"),
775
+ /* @__PURE__ */ jsx(ExpressionSlot, { children: /* @__PURE__ */ jsx(TextInput, {
776
+ id: expressionId,
777
+ "aria-labelledby": rowLabelId("expression"),
778
+ "aria-describedby": `${panelId}-sentence`,
779
+ family: FontFamily.MONO,
780
+ placeholder: "*/15 * * * *",
781
+ spellCheck: false,
782
+ value: expression,
783
+ onChange: (next) => {
784
+ if (isEditable) commit(next);
785
+ },
786
+ onEnter: close,
787
+ size: InputSize.SMALL,
788
+ variant: InputVariant.SECONDARY,
789
+ isError: isInvalid,
790
+ fillWidth: true
791
+ }) })
792
+ ] })] }), /* @__PURE__ */ jsxs(Summary, { children: [/* @__PURE__ */ jsx(SummaryIcon, { children: /* @__PURE__ */ jsx(Icon, {
793
+ component: ClockIcon,
794
+ size: 12,
795
+ variant: sentence === null ? IconVariant.TERTIARY : IconVariant.SECONDARY,
796
+ "aria-hidden": true
797
+ }) }), /* @__PURE__ */ jsx(Text, {
798
+ as: "p",
799
+ id: `${panelId}-sentence`,
800
+ size: TextSize.BODY_SM,
801
+ variant: sentence === null ? TextVariant.TERTIARY : TextVariant.PRIMARY,
802
+ children: sentence ?? CRON_INPUT_HINT
803
+ })] })]
804
+ })
805
+ });
806
+ const labelId = field?.labelId;
807
+ return /* @__PURE__ */ jsxs(Wrapper, {
808
+ ref: wrapperRef,
809
+ className: cx(WIDTH_CSS, fillWidth && FILL_WIDTH_CSS, isRoot ? className : void 0),
505
810
  ...rootStyle(isRoot, style),
506
- children: [/* @__PURE__ */ jsx(FieldContext.Provider, {
507
- value: null,
508
- children: /* @__PURE__ */ jsxs(Rows, {
509
- className: cx(fillWidth && ROWS_FILL_CSS),
510
- children: [
511
- /* @__PURE__ */ jsxs(LabelStack, { children: [rowLabel("frequency"), Object.entries(ROW_LABELS).map(([row, text]) => row === "frequency" ? null : /* @__PURE__ */ jsx(Text, {
811
+ children: [
812
+ /* @__PURE__ */ jsx(Popover, {
813
+ ref: panelRef,
814
+ trigger: PopoverTrigger.MANUAL,
815
+ role: PopoverRole.DIALOG,
816
+ variant: isGhost ? PopoverVariant.BASE : CRON_INPUT_VARIANT_TO_POPOVER_VARIANT_MAP[variant],
817
+ isOpen: isShown,
818
+ onOpenChange: setOpen,
819
+ isDisabled: !isEditable,
820
+ id: panelId,
821
+ ariaLabel: "Choose schedule",
822
+ body: /* @__PURE__ */ jsx(BareFieldContext.Provider, {
823
+ value: false,
824
+ children: body
825
+ }),
826
+ children: /* @__PURE__ */ jsx(Control, {
827
+ ref: mergedRef,
828
+ id: controlId,
829
+ role: "combobox",
830
+ tabIndex: isDisabled ? -1 : 0,
831
+ "aria-labelledby": labelId,
832
+ "aria-label": ariaLabel,
833
+ "aria-describedby": describedBy,
834
+ "aria-invalid": isInvalid ? true : void 0,
835
+ "aria-required": isRequiredState ? true : void 0,
836
+ "aria-readonly": isReadOnly ? true : void 0,
837
+ "aria-disabled": isDisabled ? true : void 0,
838
+ onKeyDown: handleControlKeyDown,
839
+ onClick: handleControlClick,
840
+ onBlur: handleFocusOut,
841
+ ...rest,
842
+ className: cx(isGhost ? GHOST_CSS : CRON_INPUT_VARIANT_TO_CSS_MAP[variant], CRON_INPUT_SIZE_TO_CSS_MAP[size], isClearable && CRON_INPUT_SIZE_TO_CLEARABLE_CSS_MAP[size], isEditable && !isInvalid && (isGhost ? GHOST_HOVER_CSS : CRON_INPUT_VARIANT_TO_HOVER_CSS_MAP[variant]), !isDisabled && FOCUS_CSS, isShown && OPEN_CSS, isInvalid && !isDisabled && ERROR_CSS, isDisabled && DISABLED_CSS, isReadOnly && READ_ONLY_CSS, isBare && "gx-bareField-BARE_FIELD_CSS-b1memido"),
843
+ children: /* @__PURE__ */ jsx(ValueDisplay, { children: /* @__PURE__ */ jsx(Text, {
512
844
  as: "span",
513
845
  size: textSize,
514
- className: LABEL_SIZER_CSS,
515
- "aria-hidden": true,
516
- children: text
517
- }, row))] }),
518
- /* @__PURE__ */ jsx(ToggleInput, {
519
- options: FREQUENCY_OPTIONS,
520
- value: frequency,
521
- onChange: changeFrequency,
522
- size: CRON_INPUT_SIZE_TO_TOGGLE_INPUT_SIZE_MAP[size],
523
- variant: CRON_INPUT_VARIANT_TO_TOGGLE_INPUT_VARIANT_MAP[variant],
524
- isGhost,
525
- "aria-labelledby": rowLabelId("frequency"),
526
- fillWidth,
527
- ...controlState
528
- }),
529
- frequency === "HOURLY" && /* @__PURE__ */ jsxs(Fragment$1, { children: [rowLabel("minute"), numberSelect("minute", "minute", MINUTE_OPTIONS)] }),
530
- frequency === "DAILY" && timeRow,
531
- frequency === "WEEKLY" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
532
- rowLabel("weekdays"),
533
- /* @__PURE__ */ jsx(ButtonGroup, {
534
- isAttached: true,
535
- "aria-labelledby": rowLabelId("weekdays"),
536
- fillWidth,
537
- children: WEEKDAYS.map(({ day, label, name }) => /* @__PURE__ */ jsx(Button, {
538
- variant: ButtonVariant.SECONDARY,
539
- size: CRON_INPUT_SIZE_TO_BUTTON_SIZE_MAP[size],
540
- label,
541
- ariaLabel: name,
542
- isActive: parts.weekdays.includes(day),
543
- isDisabled,
544
- fillWidth,
545
- ...isReadOnly ? { "aria-disabled": true } : {},
546
- onClick: () => toggleWeekday(day)
547
- }, day))
548
- }),
549
- timeRow
550
- ] }),
551
- frequency === "MONTHLY" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
552
- rowLabel("day"),
553
- numberSelect("day", "dayOfMonth", DAY_OF_MONTH_OPTIONS),
554
- timeRow
555
- ] }),
556
- frequency === "CUSTOM" && /* @__PURE__ */ jsxs(Fragment$1, { children: [rowLabel("expression"), /* @__PURE__ */ jsx(ExpressionSlot, {
557
- className: cx(fillWidth && EXPRESSION_FILL_CSS),
558
- children: /* @__PURE__ */ jsx(TextInput, {
559
- "aria-labelledby": rowLabelId("expression"),
560
- family: FontFamily.MONO,
561
- placeholder: "*/15 * * * *",
562
- spellCheck: false,
563
- value: expression,
564
- onChange: (next) => {
565
- if (!isEditable) return;
566
- lastEmittedRef.current = next;
567
- setExpression(next);
568
- },
569
- size: CRON_INPUT_SIZE_TO_INPUT_SIZE_MAP[size],
570
- variant: CRON_INPUT_VARIANT_TO_INPUT_VARIANT_MAP[variant],
571
- isGhost,
572
- fillWidth: true,
573
- ...controlState
574
- })
575
- })] })
576
- ]
846
+ ...hasValue ? { family: FontFamily.MONO } : {},
847
+ variant: isDisabled ? TextVariant.DISABLED : hasValue ? TextVariant.PRIMARY : TextVariant.TERTIARY,
848
+ lineClamp: 1,
849
+ children: hasValue ? trimmed : placeholder
850
+ }) })
851
+ })
852
+ }),
853
+ /* @__PURE__ */ jsx(VisuallyHidden, {
854
+ id: summaryId,
855
+ children: hasValue ? sentence ?? "" : ""
856
+ }),
857
+ /* @__PURE__ */ jsxs(Trailing, {
858
+ className: CRON_INPUT_SIZE_TO_TRAILING_CSS_MAP[size],
859
+ children: [isClearable && /* @__PURE__ */ jsx(ClearSlot, {
860
+ className: cx(isClearHidden && CLEAR_HIDDEN_CSS),
861
+ children: /* @__PURE__ */ jsx(Button, {
862
+ variant: ButtonVariant.TERTIARY,
863
+ size: CRON_INPUT_SIZE_TO_BUTTON_SIZE_MAP[size],
864
+ icon: XIcon,
865
+ ariaLabel: "Clear",
866
+ onClick: clear,
867
+ className: cx(size === "X_SMALL" && "gx-innerButton-X_SMALL_INNER_BUTTON_CSS-x1ep0pcd")
868
+ })
869
+ }), /* @__PURE__ */ jsx(Icon, {
870
+ component: ClockIcon,
871
+ size: CRON_INPUT_SIZE_TO_ICON_PX_MAP[size],
872
+ variant: isDisabled ? IconVariant.DISABLED : IconVariant.SECONDARY,
873
+ "aria-hidden": true
874
+ })]
577
875
  })
578
- }), /* @__PURE__ */ jsx(Text, {
579
- as: "p",
580
- id: previewId,
581
- size: helpSize,
582
- variant: isDisabled ? TextVariant.DISABLED : sentence === null ? TextVariant.TERTIARY : TextVariant.SECONDARY,
583
- children: sentence ?? CRON_INPUT_HINT
584
- })]
876
+ ]
585
877
  });
586
878
  }
587
- /** A five-field cron expression, edited through a frequency builder with a plain-English preview. */
879
+ /** A five-field cron expression in a field that opens presets and a schedule builder. */
588
880
  var CronInput = forwardRef(function CronInput(props, ref) {
589
881
  const { label, error, isRequired = false, size = "MEDIUM", fillWidth = false } = props;
590
882
  const { id, className, style } = props;
@@ -601,13 +893,13 @@ var CronInput = forwardRef(function CronInput(props, ref) {
601
893
  });
602
894
  if (ownField !== null) return /* @__PURE__ */ jsx(Field, {
603
895
  ...ownField,
604
- children: /* @__PURE__ */ jsx(CronGroup, {
896
+ children: /* @__PURE__ */ jsx(CronInputFrame, {
605
897
  cronProps: props,
606
898
  forwardedRef: ref,
607
899
  isRoot: false
608
900
  })
609
901
  });
610
- return /* @__PURE__ */ jsx(CronGroup, {
902
+ return /* @__PURE__ */ jsx(CronInputFrame, {
611
903
  cronProps: props,
612
904
  forwardedRef: ref,
613
905
  isRoot: true