@adecore/ui 0.23.0 → 0.24.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.
Files changed (129) hide show
  1. package/dist/ButtonGroup.d.ts +1 -1
  2. package/dist/ButtonGroup.js +1 -1
  3. package/dist/Checkbox.js +1 -1
  4. package/dist/ChoiceCards.js +2 -2
  5. package/dist/CloseButton.js +1 -1
  6. package/dist/ColorSwatch.d.ts +1 -1
  7. package/dist/ColorSwatch.js +1 -1
  8. package/dist/Field.d.ts +1 -1
  9. package/dist/Field.js +4 -3
  10. package/dist/IconButton.d.ts +1 -1
  11. package/dist/IconButton.js +1 -1
  12. package/dist/ListRow.d.ts +1 -1
  13. package/dist/ListRow.js +1 -1
  14. package/dist/MiddleTruncate.d.ts +8 -0
  15. package/dist/MiddleTruncate.js +59 -0
  16. package/dist/PanelHeader.d.ts +1 -1
  17. package/dist/PanelHeader.js +1 -1
  18. package/dist/SectionLabel.d.ts +1 -1
  19. package/dist/SectionLabel.js +1 -1
  20. package/dist/Select.js +1 -1
  21. package/dist/SelectSearch.js +1 -1
  22. package/dist/Slider.js +1 -1
  23. package/dist/Surface.d.ts +1 -1
  24. package/dist/Surface.js +1 -1
  25. package/dist/Switch.js +1 -1
  26. package/dist/Toasts.d.ts +1 -1
  27. package/dist/Toasts.js +4 -5
  28. package/dist/Tooltip.d.ts +1 -1
  29. package/dist/Tooltip.js +2 -2
  30. package/dist/clipboard.d.ts +1 -1
  31. package/dist/clipboard.js +12 -2
  32. package/dist/dialog/parts.d.ts +3 -3
  33. package/dist/dialog/parts.js +2 -2
  34. package/dist/file-tree/helpers.d.ts +1 -0
  35. package/dist/file-tree/helpers.js +9 -0
  36. package/dist/file-tree/parts.d.ts +1 -1
  37. package/dist/file-tree/parts.js +1 -1
  38. package/dist/file-tree/use-slots.d.ts +8 -0
  39. package/dist/file-tree/use-slots.js +64 -22
  40. package/dist/format/datetime.d.ts +1 -0
  41. package/dist/format/datetime.js +15 -0
  42. package/dist/format/index.d.ts +1 -1
  43. package/dist/format/index.js +1 -1
  44. package/dist/index.d.ts +6 -3
  45. package/dist/index.js +5 -1
  46. package/dist/key-value-list/parts.d.ts +1 -1
  47. package/dist/key-value-list/parts.js +1 -1
  48. package/dist/locales/en.d.ts +83 -0
  49. package/dist/locales/en.js +82 -0
  50. package/dist/locales/nl.d.ts +83 -0
  51. package/dist/locales/nl.js +82 -0
  52. package/dist/locales.js +3 -2
  53. package/dist/menu/parts.d.ts +7 -7
  54. package/dist/menu/parts.js +4 -4
  55. package/dist/middle-truncate.d.ts +7 -0
  56. package/dist/middle-truncate.js +34 -0
  57. package/dist/popover/parts.d.ts +7 -7
  58. package/dist/popover/parts.js +2 -2
  59. package/dist/prefetch.js +6 -1
  60. package/dist/settings/ConfirmDialog.d.ts +4 -1
  61. package/dist/settings/ConfirmDialog.js +2 -2
  62. package/dist/settings/SettingsDialog.js +1 -1
  63. package/dist/tabs/parts.d.ts +8 -4
  64. package/dist/tabs/parts.js +84 -18
  65. package/dist/testing/dedupe.js +74 -23
  66. package/dist/theme.css +24 -8
  67. package/dist/tree/parts.d.ts +1 -1
  68. package/dist/tree/parts.js +1 -1
  69. package/dist/workspace/DropPreview.d.ts +8 -0
  70. package/dist/workspace/DropPreview.js +14 -0
  71. package/dist/workspace/SplitView.d.ts +1 -1
  72. package/dist/workspace/SplitView.js +59 -18
  73. package/dist/workspace/TabStrip.d.ts +1 -1
  74. package/dist/workspace/TabStrip.js +1 -1
  75. package/dist/workspace/Workspace.d.ts +2 -2
  76. package/dist/workspace/Workspace.js +4 -2
  77. package/dist/workspace/preview.d.ts +4 -8
  78. package/dist/workspace/preview.js +17 -2
  79. package/package.json +2 -2
  80. package/src/ButtonGroup.tsx +1 -1
  81. package/src/Checkbox.tsx +1 -1
  82. package/src/ChoiceCards.tsx +2 -2
  83. package/src/CloseButton.tsx +1 -1
  84. package/src/ColorSwatch.tsx +1 -1
  85. package/src/ErrorBoundary.tsx +4 -4
  86. package/src/Field.tsx +4 -5
  87. package/src/IconButton.tsx +1 -1
  88. package/src/ListRow.tsx +1 -1
  89. package/src/MiddleTruncate.tsx +84 -0
  90. package/src/PanelHeader.tsx +1 -1
  91. package/src/SectionLabel.tsx +1 -1
  92. package/src/Select.tsx +1 -1
  93. package/src/SelectSearch.tsx +1 -1
  94. package/src/Slider.tsx +1 -1
  95. package/src/Surface.tsx +1 -1
  96. package/src/Switch.tsx +1 -1
  97. package/src/Toasts.tsx +5 -6
  98. package/src/Tooltip.tsx +5 -5
  99. package/src/clipboard.ts +11 -2
  100. package/src/dialog/parts.tsx +2 -2
  101. package/src/file-tree/helpers.ts +10 -0
  102. package/src/file-tree/parts.tsx +1 -1
  103. package/src/file-tree/use-slots.ts +68 -23
  104. package/src/format/datetime.ts +17 -0
  105. package/src/format/index.ts +1 -0
  106. package/src/index.ts +6 -3
  107. package/src/key-value-list/parts.tsx +1 -1
  108. package/src/locales/en.ts +82 -0
  109. package/src/locales/nl.ts +82 -0
  110. package/src/locales.ts +3 -2
  111. package/src/menu/parts.tsx +4 -4
  112. package/src/middle-truncate.ts +41 -0
  113. package/src/popover/parts.tsx +2 -2
  114. package/src/prefetch.ts +6 -1
  115. package/src/settings/ConfirmDialog.tsx +7 -1
  116. package/src/settings/SettingsDialog.tsx +1 -1
  117. package/src/tabs/parts.tsx +156 -54
  118. package/src/testing/dedupe.ts +87 -24
  119. package/src/theme.css +24 -8
  120. package/src/tree/parts.tsx +1 -1
  121. package/src/workspace/DropPreview.tsx +26 -0
  122. package/src/workspace/SplitView.tsx +58 -22
  123. package/src/workspace/TabStrip.tsx +1 -1
  124. package/src/workspace/Workspace.tsx +7 -3
  125. package/src/workspace/preview.ts +22 -9
  126. package/dist/locales/en.json +0 -82
  127. package/dist/locales/nl.json +0 -82
  128. package/src/locales/en.json +0 -82
  129. package/src/locales/nl.json +0 -82
@@ -1,3 +1,3 @@
1
- import { useRender } from '@base-ui-components/react/use-render';
1
+ import { useRender } from '@base-ui/react/use-render';
2
2
  export type ButtonGroupProps = useRender.ComponentProps<'div'>;
3
3
  export declare function ButtonGroup({ render, className, ref, ...props }: ButtonGroupProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
@@ -1,5 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { useRender } from '@base-ui/react/use-render';
3
3
  /* Icon buttons that belong together sit 1px apart; groups keep the wider gap of their container. */
4
4
  export function ButtonGroup({ render, className, ref, ...props }) {
5
5
  return useRender({
package/dist/Checkbox.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import clsx from 'clsx';
3
3
  import { Check, Minus } from 'lucide-react';
4
- import { Checkbox as BaseCheckbox } from '@base-ui-components/react/checkbox';
4
+ import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
5
5
  import { Icon } from './Icon.js';
6
6
  /* Picked or not, as one item out of a list or an option that waits for a Save button. */
7
7
  export function Checkbox({ checked, onCheckedChange, label, indeterminate = false, disabled, className, ref }) {
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Fragment, useId } from 'react';
3
3
  import clsx from 'clsx';
4
- import { Radio } from '@base-ui-components/react/radio';
5
- import { RadioGroup } from '@base-ui-components/react/radio-group';
4
+ import { Radio } from '@base-ui/react/radio';
5
+ import { RadioGroup } from '@base-ui/react/radio-group';
6
6
  import { Icon } from './Icon.js';
7
7
  function ChoiceCard({ choice, radio }) {
8
8
  const id = useId();
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { X } from 'lucide-react';
3
- import { Dialog } from '@base-ui-components/react/dialog';
3
+ import { Dialog } from '@base-ui/react/dialog';
4
4
  import { IconButton } from './IconButton.js';
5
5
  /* The close button in the header of every panel and dialog. In a dialog it is that dialog's own close part. */
6
6
  export function CloseButton({ label, kbd, size, className, ref, ...closes }) {
@@ -1,5 +1,5 @@
1
1
  import type { ComponentProps } from 'react';
2
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { useRender } from '@base-ui/react/use-render';
3
3
  declare const PICKED_RING: {
4
4
  readonly surface: 'ring-2 ring-accent ring-offset-2 ring-offset-surface';
5
5
  readonly popup: 'ring-2 ring-accent ring-offset-1 ring-offset-surface-raised';
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import clsx from 'clsx';
3
3
  import { Check } from 'lucide-react';
4
- import { useRender } from '@base-ui-components/react/use-render';
4
+ import { useRender } from '@base-ui/react/use-render';
5
5
  import { Icon } from './Icon.js';
6
6
  /* `focus-ring` for the swatch that is a menu item rather than a button, which the keyboard reaches all the same. */
7
7
  const SWATCH = 'focus-ring grid h-6 w-6 place-items-center rounded-full text-accent-text';
package/dist/Field.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode, type Ref } from 'react';
2
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { useRender } from '@base-ui/react/use-render';
3
3
  export type FieldHintProps = useRender.ComponentProps<'p'>;
4
4
  export declare function FieldHint({ render, className, ref, ...props }: FieldHintProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
5
5
  export type FormErrorProps = useRender.ComponentProps<'p'>;
package/dist/Field.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useId } from 'react';
2
+ import { useId, useMemo } from 'react';
3
3
  import clsx from 'clsx';
4
- import { useRender } from '@base-ui-components/react/use-render';
4
+ import { useRender } from '@base-ui/react/use-render';
5
5
  import { FieldContext } from './field-context.js';
6
6
  import { SectionLabel } from './SectionLabel.js';
7
7
  const HINT = 'text-xs text-text-muted';
@@ -45,5 +45,6 @@ export function Field({ label, hint, error, group = false, orientation = 'vertic
45
45
  const hasHint = isShown(hint);
46
46
  const hasError = isShown(error);
47
47
  const describedBy = [hasHint ? `${id}-hint` : null, hasError ? `${id}-error` : null].filter((part) => part !== null).join(' ') || undefined;
48
- return (_jsxs("div", { ref: ref, role: group ? 'group' : undefined, "aria-labelledby": group && hasLabel ? `${id}-label` : undefined, "aria-describedby": group ? describedBy : undefined, className: clsx(horizontal ? 'grid grid-cols-[112px_minmax(0,1fr)] gap-x-3 gap-y-1.5' : 'flex flex-col gap-1.5', className), children: [hasLabel && (_jsx(FieldLabel, { id: id, group: group, horizontal: horizontal, children: label })), _jsx(FieldContext, { value: group ? null : { id: `${id}-control`, describedBy, invalid: hasError }, children: horizontal ? _jsx("div", { className: "col-start-2 min-w-0", children: children }) : children }), hasHint && (_jsx("p", { id: `${id}-hint`, className: clsx(HINT, horizontal && 'col-start-2'), children: hint })), hasError && (_jsx("p", { id: `${id}-error`, role: "alert", className: clsx(ERROR, 'break-words', horizontal && 'col-start-2'), children: error }))] }));
48
+ const control = useMemo(() => (group ? null : { id: `${id}-control`, describedBy, invalid: hasError }), [group, id, describedBy, hasError]);
49
+ return (_jsxs("div", { ref: ref, role: group ? 'group' : undefined, "aria-labelledby": group && hasLabel ? `${id}-label` : undefined, "aria-describedby": group ? describedBy : undefined, className: clsx(horizontal ? 'grid grid-cols-[112px_minmax(0,1fr)] gap-x-3 gap-y-1.5' : 'flex flex-col gap-1.5', className), children: [hasLabel && (_jsx(FieldLabel, { id: id, group: group, horizontal: horizontal, children: label })), _jsx(FieldContext, { value: control, children: horizontal ? _jsx("div", { className: "col-start-2 min-w-0", children: children }) : children }), hasHint && (_jsx("p", { id: `${id}-hint`, className: clsx(HINT, horizontal && 'col-start-2'), children: hint })), hasError && (_jsx("p", { id: `${id}-error`, role: "alert", className: clsx(ERROR, 'break-words', horizontal && 'col-start-2'), children: error }))] }));
49
50
  }
@@ -1,6 +1,6 @@
1
1
  import type { ComponentProps, ReactNode } from 'react';
2
2
  import type { LucideIcon } from 'lucide-react';
3
- import { useRender } from '@base-ui-components/react/use-render';
3
+ import { useRender } from '@base-ui/react/use-render';
4
4
  import type { Shortcut } from './shortcut.ts';
5
5
  import { type TooltipSide } from './Tooltip.tsx';
6
6
  import { type IconButtonSize } from './icon-button-size.ts';
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import clsx from 'clsx';
3
- import { useRender } from '@base-ui-components/react/use-render';
3
+ import { useRender } from '@base-ui/react/use-render';
4
4
  import { Icon } from './Icon.js';
5
5
  import { Spinner } from './Spinner.js';
6
6
  import { Tooltip } from './Tooltip.js';
package/dist/ListRow.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { useRender } from '@base-ui-components/react/use-render';
1
+ import { useRender } from '@base-ui/react/use-render';
2
2
  declare const VARIANT: {
3
3
  readonly flat: 'flex h-7 items-center';
4
4
  readonly inset: 'flex h-8 items-center rounded-md px-2';
package/dist/ListRow.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { useRender } from '@base-ui/react/use-render';
3
3
  const VARIANT = {
4
4
  /* A row of a list that reads as a table and fills the width of its panel: commits, processes,
5
5
  branches. Square, so the rows stack into one block. It carries no padding; the caller pads 12px
@@ -0,0 +1,8 @@
1
+ import { type Ref } from 'react';
2
+ export interface MiddleTruncateProps {
3
+ text: string;
4
+ tooltip?: boolean;
5
+ className?: string;
6
+ ref?: Ref<HTMLSpanElement>;
7
+ }
8
+ export declare function MiddleTruncate({ text, tooltip, className, ref }: MiddleTruncateProps): import("react").JSX.Element;
@@ -0,0 +1,59 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useMemo, useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import { fitMiddle } from './middle-truncate.js';
5
+ import { mergeRefs } from './merge-refs.js';
6
+ import { Tooltip } from './Tooltip.js';
7
+ let canvas = null;
8
+ /* How wide a text is drawn in the face of `element`, or null where the page cannot tell (no canvas). */
9
+ function textMeasure(element) {
10
+ const view = element.ownerDocument.defaultView;
11
+ canvas ??= element.ownerDocument.createElement('canvas');
12
+ const context = canvas.getContext('2d');
13
+ if (context === null || view === null) {
14
+ return null;
15
+ }
16
+ const style = view.getComputedStyle(element);
17
+ context.font = style.font || `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
18
+ if ('letterSpacing' in context) {
19
+ context.letterSpacing = style.letterSpacing === 'normal' ? '0px' : style.letterSpacing;
20
+ }
21
+ return (text) => context.measureText(text).width;
22
+ }
23
+ function sameCut(first, second) {
24
+ return first?.head === second?.head && first?.tail === second?.tail;
25
+ }
26
+ /*
27
+ * A path or a branch name on one line that keeps its start and its end and cuts the middle. It asks
28
+ * for the width of the whole text and measures what it gets. The part it cuts stays in the element
29
+ * at no width, so selecting, copying and finding read the whole text.
30
+ */
31
+ export function MiddleTruncate({ text, tooltip = true, className, ref }) {
32
+ const [element, setElement] = useState(null);
33
+ const [cut, setCut] = useState(null);
34
+ const rootRef = useMemo(() => mergeRefs(setElement, ref), [ref]);
35
+ useLayoutEffect(() => {
36
+ if (element === null) {
37
+ return;
38
+ }
39
+ const update = () => {
40
+ const measure = textMeasure(element);
41
+ // Layout rounds what a canvas measures, so a text a fraction wider than its box still fits.
42
+ const next = measure === null ? null : fitMiddle(text, element.getBoundingClientRect().width + 0.5, measure);
43
+ setCut((current) => (sameCut(current, next) ? current : next));
44
+ };
45
+ update();
46
+ if (typeof ResizeObserver === 'undefined') {
47
+ return;
48
+ }
49
+ const observer = new ResizeObserver(update);
50
+ observer.observe(element);
51
+ return () => observer.disconnect();
52
+ }, [element, text]);
53
+ const content = (_jsx("span", { ref: rootRef, className: clsx('middle-truncate', className), "data-text": text, children: cut === null ? (text) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "middle-truncate-head", children: cut.head }), _jsx("span", { className: "middle-truncate-middle", children: cut.middle }), cut.tail] })) }));
54
+ // Only a cut text has more to say, and a long list of whole ones keeps no hover handlers.
55
+ if (cut === null || !tooltip) {
56
+ return content;
57
+ }
58
+ return _jsx(Tooltip, { label: text, children: content });
59
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { useRender } from '@base-ui/react/use-render';
3
3
  export type PanelHeaderProps = useRender.ComponentProps<'header'> & {
4
4
  title?: ReactNode;
5
5
  };
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import clsx from 'clsx';
3
- import { useRender } from '@base-ui-components/react/use-render';
3
+ import { useRender } from '@base-ui/react/use-render';
4
4
  import { SectionLabel } from './SectionLabel.js';
5
5
  /*
6
6
  * The bar across the top of a side panel, as tall as a 48px toolbar beside it: the panel's name,
@@ -1,3 +1,3 @@
1
- import { useRender } from '@base-ui-components/react/use-render';
1
+ import { useRender } from '@base-ui/react/use-render';
2
2
  export type SectionLabelProps = useRender.ComponentProps<'span'>;
3
3
  export declare function SectionLabel({ render, className, ref, ...props }: SectionLabelProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
@@ -1,5 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { useRender } from '@base-ui/react/use-render';
3
3
  /* A label takes the line height of the row it sits in, which is why the size carries `/[inherit]`. */
4
4
  const SECTION_LABEL = 'text-xs/[inherit] font-medium text-text-faint';
5
5
  /*
package/dist/Select.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Select as BaseSelect } from '@base-ui-components/react/select';
2
+ import { Select as BaseSelect } from '@base-ui/react/select';
3
3
  import clsx from 'clsx';
4
4
  import { ChevronDown } from 'lucide-react';
5
5
  import { useTranslation } from 'react-i18next';
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
- import { Combobox } from '@base-ui-components/react/combobox';
3
+ import { Combobox } from '@base-ui/react/combobox';
4
4
  import clsx from 'clsx';
5
5
  import { ChevronDown, Search } from 'lucide-react';
6
6
  import { useTranslation } from 'react-i18next';
package/dist/Slider.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useId } from 'react';
3
3
  import clsx from 'clsx';
4
- import { Slider as BaseSlider } from '@base-ui-components/react/slider';
4
+ import { Slider as BaseSlider } from '@base-ui/react/slider';
5
5
  import { formatFixed } from './format/number.js';
6
6
  import { placesOf } from './step-places.js';
7
7
  /* A number picked along a range: the label and the value on one line, the track under them. */
package/dist/Surface.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { useRender } from '@base-ui-components/react/use-render';
1
+ import { useRender } from '@base-ui/react/use-render';
2
2
  export type SurfaceProps = useRender.ComponentProps<'div'>;
3
3
  export declare function Surface({ render, className, ref, ...props }: SurfaceProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
package/dist/Surface.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import clsx from 'clsx';
2
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { useRender } from '@base-ui/react/use-render';
3
3
  const FLOAT = 'border border-border bg-[color-mix(in_srgb,var(--surface-raised)_88%,transparent)] shadow-float backdrop-blur-[14px]';
4
4
  /*
5
5
  * The glass card that floats over the content under it: a dock, a banner, a toast, a find bar, a
package/dist/Switch.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import clsx from 'clsx';
3
- import { Switch as BaseSwitch } from '@base-ui-components/react/switch';
3
+ import { Switch as BaseSwitch } from '@base-ui/react/switch';
4
4
  /* On or off, as a switch rather than a checkbox: a setting that takes effect the moment it flips. */
5
5
  export function Switch({ checked, onCheckedChange, label, disabled, className, ref }) {
6
6
  return (_jsx(BaseSwitch.Root, { ref: ref, checked: checked, onCheckedChange: (next) => onCheckedChange(next), "aria-label": label, disabled: disabled,
package/dist/Toasts.d.ts CHANGED
@@ -6,4 +6,4 @@ export interface ToastsProps<T extends Toast> {
6
6
  className?: string;
7
7
  ref?: Ref<HTMLDivElement>;
8
8
  }
9
- export declare function Toasts<T extends Toast>({ store, footer, className, ref }: ToastsProps<T>): import("react").JSX.Element | null;
9
+ export declare function Toasts<T extends Toast>({ store, footer, className, ref }: ToastsProps<T>): import("react").JSX.Element;
package/dist/Toasts.js CHANGED
@@ -3,6 +3,7 @@ import { useState } from 'react';
3
3
  import clsx from 'clsx';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { CircleAlert, CircleCheck, Trash, X } from 'lucide-react';
6
+ import { Button } from './Button.js';
6
7
  import { Icon } from './Icon.js';
7
8
  import { IconButton } from './IconButton.js';
8
9
  import { Kbd } from './Kbd.js';
@@ -33,7 +34,7 @@ function TimerRing({ deadline, onDismiss }) {
33
34
  return (_jsxs(IconButton, { size: "xs", label: t('action.dismiss'), className: "group/ring relative -my-px", onClick: onDismiss, children: [_jsxs("svg", { width: 16, height: 16, viewBox: "0 0 16 16", className: "-rotate-90", "aria-hidden": true, children: [_jsx("circle", { cx: 8, cy: 8, r: 6, fill: "none", strokeWidth: 2, className: "stroke-border" }), _jsx("circle", { cx: 8, cy: 8, r: 6, fill: "none", strokeWidth: 2, pathLength: 1, strokeDasharray: 1, className: "toast-timer-arc stroke-current", style: { animationDuration: `${lifetime}ms`, animationDelay: `${-elapsed}ms` } })] }), _jsx(Icon, { icon: X, size: 8, className: "absolute opacity-0 group-hover/ring:opacity-100 group-focus-visible/ring:opacity-100" })] }));
34
35
  }
35
36
  function ActionButton({ action }) {
36
- return (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("button", { className: "rounded-sm text-sm font-semibold text-text hover:text-text-muted", onClick: action.run, children: action.label }), action.shortcut && _jsx(Kbd, { shortcut: action.shortcut, className: "font-sans text-xs text-text-faint" })] }));
37
+ return (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Button, { size: "xs", onClick: action.run, children: action.label }), action.shortcut && _jsx(Kbd, { shortcut: action.shortcut, className: "font-sans text-xs text-text-faint" })] }));
37
38
  }
38
39
  function ToastCard({ toast, footer, onDismiss }) {
39
40
  const { t } = useTranslation('ui');
@@ -42,7 +43,7 @@ function ToastCard({ toast, footer, onDismiss }) {
42
43
  const actions = [...(toast.action === undefined ? [] : [toast.action]), ...(toast.actions ?? [])];
43
44
  // An offer that runs out shows how long it has left instead.
44
45
  const countdown = actions.length > 0 ? toast.deadline : undefined;
45
- return (_jsxs(Surface, { className: "group flex items-start gap-3 rounded-[10px] py-2.5 pr-2 pl-3", children: [_jsx("span", { className: clsx(TITLE_LINE, 'w-4 justify-center'), children: toast.kind === 'progress' ? (_jsx(Spinner, { size: 16, className: TONE.progress })) : (_jsx(Icon, { icon: ICON[toast.kind], size: 16, className: TONE[toast.kind] })) }), _jsxs("div", { className: "flex min-w-0 grow flex-col gap-1", children: [_jsx("span", { className: "text-sm break-words text-text", children: toast.title }), toast.description !== undefined && toast.description !== '' && (_jsx("span", { className: "text-xs break-words text-pretty text-text-muted", children: toast.description })), actions.length > 1 && (_jsx("span", { className: "mt-1 flex flex-wrap items-center gap-x-4 gap-y-1", children: actions.map((action) => (_jsx(ActionButton, { action: action }, action.label))) })), footer?.(toast)] }), actions.length === 1 && (_jsx("span", { className: TITLE_LINE, children: _jsx(ActionButton, { action: actions[0] }) })), countdown !== undefined ? (_jsx(TimerRing, { deadline: countdown, onDismiss: onDismiss }, countdown.start)) : (_jsx(IconButton, { icon: X, size: "xs", label: t('action.dismiss'), className: clsx('-my-px', leaves && 'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100'), onClick: onDismiss }))] }));
46
+ return (_jsxs(Surface, { className: "group flex items-start gap-3 rounded-[10px] py-2.5 pr-2 pl-3", children: [_jsx("span", { className: clsx(TITLE_LINE, 'w-4 justify-center'), children: toast.kind === 'progress' ? (_jsx(Spinner, { size: 16, className: TONE.progress })) : (_jsx(Icon, { icon: ICON[toast.kind], size: 16, className: TONE[toast.kind] })) }), _jsxs("div", { className: "flex min-w-0 grow flex-col gap-1", children: [_jsx("span", { className: "text-sm break-words text-text", children: toast.title }), toast.description !== undefined && toast.description !== '' && (_jsx("span", { className: "text-xs break-words text-pretty text-text-muted", children: toast.description })), actions.length > 1 && (_jsx("span", { className: "mt-1 -ml-2 flex flex-wrap items-center gap-x-2 gap-y-1", children: actions.map((action) => (_jsx(ActionButton, { action: action }, action.label))) })), footer?.(toast)] }), actions.length === 1 && (_jsx("span", { className: TITLE_LINE, children: _jsx(ActionButton, { action: actions[0] }) })), countdown !== undefined ? (_jsx(TimerRing, { deadline: countdown, onDismiss: onDismiss }, countdown.start)) : (_jsx(IconButton, { icon: X, size: "xs", label: t('action.dismiss'), className: clsx('-my-px', leaves && 'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100'), onClick: onDismiss }))] }));
46
47
  }
47
48
  /*
48
49
  * Bottom right, over everything: what an action is doing while it runs and how it went when it is
@@ -51,8 +52,6 @@ function ToastCard({ toast, footer, onDismiss }) {
51
52
  */
52
53
  export function Toasts({ store, footer, className, ref }) {
53
54
  const toasts = store((state) => state.toasts);
54
- if (toasts.length === 0) {
55
- return null;
56
- }
55
+ // Stays mounted with nothing in it: a screen reader skips what a live region says the moment it is inserted.
57
56
  return (_jsx("div", { ref: ref, className: clsx('fixed right-4 bottom-4 z-(--z-popup) flex w-90 max-w-[calc(100vw-32px)] flex-col gap-2', className), role: "status", "aria-live": "polite", children: toasts.map((toast) => (_jsx(ToastCard, { toast: toast, footer: footer, onDismiss: () => store.getState().dismiss(toast.id) }, toast.id))) }));
58
57
  }
package/dist/Tooltip.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
2
- import { Tooltip as BaseTooltip } from '@base-ui-components/react/tooltip';
2
+ import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
3
3
  import type { Shortcut } from './shortcut.ts';
4
4
  export type TooltipSide = 'top' | 'bottom' | 'left' | 'right';
5
5
  export declare function TooltipProvider({ children }: {
package/dist/Tooltip.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback } from 'react';
3
- import { Tooltip as BaseTooltip } from '@base-ui-components/react/tooltip';
3
+ import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
4
4
  import { Kbd } from './Kbd.js';
5
5
  import { registerShortcutHint } from './shortcut-hints.js';
6
6
  /* One provider per app. Tooltips share a delay, so moving along a row of buttons feels instant. `UIProvider` mounts it. */
@@ -16,6 +16,6 @@ export function Tooltip({ label, open, onOpenChange, anchor, kbd, side = 'top',
16
16
  }
17
17
  return registerShortcutHint(element, kbd);
18
18
  }, [kbd]);
19
- return (_jsxs(BaseTooltip.Root, { disabled: disabled, open: open, onOpenChange: onOpenChange, disableHoverablePopup: children === undefined, children: [children && _jsx(BaseTooltip.Trigger, { ref: hintRef, render: children, "aria-label": name && typeof label === 'string' ? label : undefined }), _jsx(BaseTooltip.Portal, { children: _jsx(BaseTooltip.Positioner, { anchor: anchor, side: side, sideOffset: sideOffset, className: "tooltip-positioner", children: _jsx(BaseTooltip.Popup, { role: "tooltip", className: "tooltip-popup", children: _jsxs(BaseTooltip.Viewport, { className: "max-w-72 px-[9px] py-[5px] text-pretty", children: [_jsx("span", { className: "wrap-anywhere", children: label }), kbd !== undefined &&
19
+ return (_jsxs(BaseTooltip.Root, { disabled: disabled, open: open, onOpenChange: onOpenChange, disableHoverablePopup: children === undefined, children: [children && _jsx(BaseTooltip.Trigger, { ref: hintRef, render: children, "aria-label": name && typeof label === 'string' ? label : undefined }), _jsx(BaseTooltip.Portal, { children: _jsx(BaseTooltip.Positioner, { anchor: anchor, side: side, sideOffset: sideOffset, className: "tooltip-positioner", children: _jsx(BaseTooltip.Popup, { role: "tooltip", className: "tooltip-popup", children: _jsxs("div", { className: "max-w-72 px-[9px] py-[5px] text-pretty", children: [_jsx("span", { className: "wrap-anywhere", children: label }), kbd !== undefined &&
20
20
  (typeof kbd === 'string' ? (_jsx(Kbd, { variant: "inline", className: "ml-2", children: kbd })) : (_jsx(Kbd, { shortcut: kbd, variant: "inline", className: "ml-2" })))] }) }) }) })] }));
21
21
  }
@@ -1,2 +1,2 @@
1
- export declare const copyText: (text: string) => void;
1
+ export declare const copyText: (text: string) => Promise<boolean>;
2
2
  export declare const readClipboardText: () => Promise<string>;
package/dist/clipboard.js CHANGED
@@ -2,8 +2,18 @@
2
2
  * The clipboard, as the app uses it. A browser hands text over only to a document that is focused
3
3
  * and allowed, and there is nothing a person can do about a refusal, so both sides stay quiet.
4
4
  */
5
- export const copyText = (text) => {
6
- void navigator.clipboard?.writeText(text).catch(() => undefined);
5
+ /* Resolves to whether the text got there, for a caller that tells the person; it never rejects. */
6
+ export const copyText = async (text) => {
7
+ try {
8
+ if (!navigator.clipboard) {
9
+ return false;
10
+ }
11
+ await navigator.clipboard.writeText(text);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
7
17
  };
8
18
  /* What is on the clipboard, or nothing at all when the browser will not say. */
9
19
  export const readClipboardText = async () => {
@@ -1,12 +1,12 @@
1
1
  import { type ComponentProps } from 'react';
2
- import { Dialog as BaseDialog } from '@base-ui-components/react/dialog';
3
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { Dialog as BaseDialog } from '@base-ui/react/dialog';
3
+ import { useRender } from '@base-ui/react/use-render';
4
4
  declare const SIZE: {
5
5
  readonly sm: 'w-[420px] p-5';
6
6
  };
7
7
  export declare function DialogRoot({ open, defaultOpen, onOpenChange, ...props }: ComponentProps<typeof BaseDialog.Root>): import("react").JSX.Element;
8
8
  export declare const DialogTrigger: BaseDialog.Trigger;
9
- export declare const DialogClose: import("react").ForwardRefExoticComponent<import("@base-ui-components/react/dialog").DialogCloseProps & import("react").RefAttributes<HTMLButtonElement>>;
9
+ export declare const DialogClose: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react").DialogCloseProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
10
10
  export type DialogPopupProps = ComponentProps<typeof BaseDialog.Popup> & {
11
11
  size?: keyof typeof SIZE;
12
12
  nested?: boolean;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useState } from 'react';
3
3
  import clsx from 'clsx';
4
- import { Dialog as BaseDialog } from '@base-ui-components/react/dialog';
5
- import { useRender } from '@base-ui-components/react/use-render';
4
+ import { Dialog as BaseDialog } from '@base-ui/react/dialog';
5
+ import { useRender } from '@base-ui/react/use-render';
6
6
  import { withClass } from '../class-name.js';
7
7
  import { useDialogLayer } from '../dialog-layer.js';
8
8
  /* Whether the dialog is open, for its popup to decide whether it stacks over another one. */
@@ -6,6 +6,7 @@ export interface SortRow {
6
6
  export declare function directoryHandle(model: FileTree, path: string): FileTreeDirectoryHandle | null;
7
7
  export declare function pathOfRow(row: FileTreeVisibleRow): string;
8
8
  export declare function visibleRows(model: FileTree): FileTreeVisibleRow[];
9
+ export declare function visibleRowsAround(model: FileTree, start: number, count: number): FileTreeVisibleRow[];
9
10
  export declare function applyExpansion(model: FileTree, collapsed: ReadonlySet<string>, keyOf?: FoldKeyOf): void;
10
11
  export declare function resetExpandedPaths(model: FileTree, paths: readonly string[], expanded: ReadonlySet<string>): void;
11
12
  export declare function rowPathOf(event: {
@@ -10,6 +10,15 @@ export function pathOfRow(row) {
10
10
  export function visibleRows(model) {
11
11
  return model.getVisibleRows(0, model.getVisibleCount()).map((row) => ({ ...row, path: pathOfRow(row) }));
12
12
  }
13
+ /* The `count` rows from `start` and one on each side, for a neighbor's selection, without copying the whole list. */
14
+ export function visibleRowsAround(model, start, count) {
15
+ const first = Math.max(0, start - 1);
16
+ const last = Math.min(model.getVisibleCount() - 1, start + count);
17
+ if (last < first) {
18
+ return [];
19
+ }
20
+ return model.getVisibleRows(first, last).map((row) => ({ ...row, path: pathOfRow(row) }));
21
+ }
13
22
  export function applyExpansion(model, collapsed, keyOf) {
14
23
  for (let pass = 0; pass < EXPANSION_PASSES; pass++) {
15
24
  const { collapse, expand } = expansionChanges(visibleRows(model), collapsed, keyOf);
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
- import { useRender } from '@base-ui-components/react/use-render';
2
+ import { useRender } from '@base-ui/react/use-render';
3
3
  import { type FileTreeProps as PierreProps } from '@pierre/trees/react';
4
4
  import type { FileTree, FileTreeVisibleRow } from '@pierre/trees';
5
5
  export type FileTreeRootProps = useRender.ComponentProps<'div'> & {
@@ -1,7 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
- import { useRender } from '@base-ui-components/react/use-render';
4
+ import { useRender } from '@base-ui/react/use-render';
5
5
  import { FileTree as PierreFileTree } from '@pierre/trees/react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import clsx from 'clsx';
@@ -4,4 +4,12 @@ export interface RowSlot {
4
4
  control: HTMLElement;
5
5
  decoration: HTMLElement;
6
6
  }
7
+ export interface Port {
8
+ rowElement: HTMLElement;
9
+ control: HTMLElement;
10
+ decoration: HTMLElement;
11
+ lightControl: HTMLElement;
12
+ lightDecoration: HTMLElement;
13
+ }
14
+ export declare function positionPorts(ports: Iterable<Port>): void;
7
15
  export declare function useFileTreeSlots(model: FileTree, frame: HTMLElement | null, label: string): RowSlot[];
@@ -1,7 +1,64 @@
1
1
  import { useEffect, useId, useState } from 'react';
2
2
  import { flushSync } from 'react-dom';
3
- import { pathOfRow, visibleRows } from './helpers.js';
3
+ import { pathOfRow, visibleRows, visibleRowsAround } from './helpers.js';
4
4
  import { FILE_TREE_CSS } from './style.js';
5
+ /*
6
+ * The rows of the mounted window, read from the spacer beta.6 puts above it (its first index times the
7
+ * row height), so a scroll frame copies those and not every visible row. The whole list stands in when
8
+ * the spacer is missing or the model already moved past what is mounted.
9
+ */
10
+ function mountedRows(model, shadow, elements) {
11
+ const spacer = shadow.querySelector('[data-file-tree-virtualized-sticky-offset]');
12
+ const offset = spacer === null ? Number.NaN : Number.parseFloat(spacer.style.height);
13
+ const itemHeight = model.getItemHeight();
14
+ if (!Number.isFinite(offset) || itemHeight <= 0) {
15
+ return visibleRows(model);
16
+ }
17
+ // A folder pinned to the top leaves the flow, but still takes its index in the window.
18
+ const pinned = shadow.querySelectorAll('[data-file-tree-sticky-row="true"]').length;
19
+ const rows = visibleRowsAround(model, Math.round(offset / itemHeight), elements.length + pinned);
20
+ const paths = new Set(rows.map((row) => row.path));
21
+ return elements.every((element) => element.dataset.itemPath === undefined || paths.has(element.dataset.itemPath)) ? rows : visibleRows(model);
22
+ }
23
+ /*
24
+ * Lines the ports up with their rows: every size first, then the spaces they reserve, then every offset,
25
+ * then the ports, so a pass forces two layouts at most however many rows are mounted.
26
+ */
27
+ export function positionPorts(ports) {
28
+ const spaced = [...ports].map((port) => ({
29
+ port,
30
+ control: port.lightControl.childNodes.length > 0 ? Math.ceil(port.lightControl.getBoundingClientRect().width) + 6 : 0,
31
+ decoration: port.lightDecoration.childNodes.length > 0 ? Math.ceil(port.lightDecoration.getBoundingClientRect().width) + 6 : 0
32
+ }));
33
+ for (const { port, control, decoration } of spaced) {
34
+ setSpace(port.rowElement, '--adecore-tree-control-space', control);
35
+ setSpace(port.rowElement, '--adecore-tree-decoration-space', decoration);
36
+ }
37
+ const placed = spaced.map(({ port, control, decoration }) => {
38
+ const row = port.rowElement;
39
+ const content = row.querySelector('[data-item-section="content"]');
40
+ const icon = row.querySelector('[data-item-section="icon"]');
41
+ return {
42
+ port,
43
+ top: `${row.offsetTop}px`,
44
+ controlLeft: `${row.offsetLeft + (icon?.offsetLeft ?? 4) - control}px`,
45
+ decorationLeft: `${row.offsetLeft + (content === null ? row.offsetWidth - decoration - 2 : content.offsetLeft + content.offsetWidth + 6)}px`
46
+ };
47
+ });
48
+ for (const { port, top, controlLeft, decorationLeft } of placed) {
49
+ port.control.style.top = top;
50
+ port.control.style.left = controlLeft;
51
+ port.decoration.style.top = top;
52
+ port.decoration.style.left = decorationLeft;
53
+ }
54
+ }
55
+ // An unchanged space is left alone, so a pass that only scrolled measures once.
56
+ function setSpace(row, name, space) {
57
+ const value = `${space}px`;
58
+ if (row.style.getPropertyValue(name) !== value) {
59
+ row.style.setProperty(name, value);
60
+ }
61
+ }
5
62
  // beta.6 owns button rows. Sibling slots keep real controls outside those buttons and in the app's stylesheet.
6
63
  export function useFileTreeSlots(model, frame, label) {
7
64
  const prefix = useId().replaceAll(':', '');
@@ -17,23 +74,7 @@ export function useFileTreeSlots(model, frame, label) {
17
74
  let disposed = false;
18
75
  let pending = false;
19
76
  let snapshot = null;
20
- const position = () => {
21
- for (const port of ports.values()) {
22
- const row = port.rowElement;
23
- const content = row.querySelector('[data-item-section="content"]');
24
- const icon = row.querySelector('[data-item-section="icon"]');
25
- const control = port.lightControl;
26
- const decoration = port.lightDecoration;
27
- const controlSpace = control.childNodes.length > 0 ? Math.ceil(control.getBoundingClientRect().width) + 6 : 0;
28
- const decorationSpace = decoration.childNodes.length > 0 ? Math.ceil(decoration.getBoundingClientRect().width) + 6 : 0;
29
- row.style.setProperty('--adecore-tree-control-space', `${controlSpace}px`);
30
- row.style.setProperty('--adecore-tree-decoration-space', `${decorationSpace}px`);
31
- port.control.style.top = `${row.offsetTop}px`;
32
- port.control.style.left = `${row.offsetLeft + (icon?.offsetLeft ?? 4) - controlSpace}px`;
33
- port.decoration.style.top = `${row.offsetTop}px`;
34
- port.decoration.style.left = `${row.offsetLeft + (content === null ? row.offsetWidth - decorationSpace - 2 : content.offsetLeft + content.offsetWidth + 6)}px`;
35
- }
36
- };
77
+ const position = () => positionPorts(ports.values());
37
78
  const resize = new ResizeObserver(position);
38
79
  const dropPort = (port) => {
39
80
  port.control.remove();
@@ -87,11 +128,12 @@ export function useFileTreeSlots(model, frame, label) {
87
128
  resize.observe(current);
88
129
  }
89
130
  shadow.querySelector('[role="tree"]')?.setAttribute('aria-label', label);
90
- const rows = visibleRows(model);
91
- const byPath = new Map(rows.map((row) => [pathOfRow(row), row]));
92
131
  const elements = [
93
132
  ...shadow.querySelectorAll('[data-type="item"]:not([data-item-parked="true"]):not([data-file-tree-sticky-row="true"])')
94
133
  ];
134
+ const rows = mountedRows(model, shadow, elements);
135
+ const byPath = new Map(rows.map((row) => [pathOfRow(row), row]));
136
+ const byIndex = new Map(rows.map((row) => [row.index, row]));
95
137
  const live = new Set(elements);
96
138
  for (const [element, port] of ports) {
97
139
  if (!live.has(element)) {
@@ -107,8 +149,8 @@ export function useFileTreeSlots(model, frame, label) {
107
149
  continue;
108
150
  }
109
151
  element.dataset.interactive = 'true';
110
- element.dataset.joinedStart = String(row.isSelected && rows[row.index - 1]?.isSelected === true);
111
- element.dataset.joinedEnd = String(row.isSelected && rows[row.index + 1]?.isSelected === true);
152
+ element.dataset.joinedStart = String(row.isSelected && byIndex.get(row.index - 1)?.isSelected === true);
153
+ element.dataset.joinedEnd = String(row.isSelected && byIndex.get(row.index + 1)?.isSelected === true);
112
154
  let port = ports.get(element);
113
155
  if (port === undefined) {
114
156
  serial++;
@@ -9,3 +9,4 @@ export declare const formatDayClock: (at: Date | number) => string;
9
9
  export declare const formatNumericDate: (at: Date | number) => string;
10
10
  export declare const isSameDay: (a: Date | number, b: Date | number) => boolean;
11
11
  export declare const formatMoment: (at: Date | number, now?: Date | number) => string;
12
+ export declare const formatAge: (at: Date | number, now?: Date | number) => string;