@valu/block-components 0.3.1 → 0.4.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.
@@ -23,6 +23,12 @@ interface DocumentItem {
23
23
  modified?: string;
24
24
  /** Raw REST record so consumer filters can reach beyond the normalized shape. */
25
25
  raw?: unknown;
26
+ /**
27
+ * Editor-authored display name that overrides `title` in consumer UIs.
28
+ * Never derived from the REST record — set only by the user via the
29
+ * per-item label field (see `enableItemLabels`).
30
+ */
31
+ label?: string;
26
32
  }
27
33
  /** A picked document resolved against the current entity store. */
28
34
  interface ResolvedDocumentItem extends DocumentItem {
@@ -47,6 +53,7 @@ interface DocumentPickerLabels {
47
53
  missing?: string;
48
54
  openLink?: string;
49
55
  extensionBadge?: string;
56
+ itemLabel?: string;
50
57
  }
51
58
  interface RequestContext {
52
59
  query: string;
@@ -100,6 +107,14 @@ interface DocumentPickerProps {
100
107
  uniqueItems?: boolean;
101
108
  /** Show the "Browse Media Library" button. @default true */
102
109
  enableMediaLibrary?: boolean;
110
+ /**
111
+ * Make each selected row's display name editable in place: the title is
112
+ * rendered as a text input prefilled with the effective name. Edits are
113
+ * stored on the item as `label` (the attachment itself is untouched);
114
+ * clearing the field — or typing the original title back — removes the
115
+ * override and falls back to the attachment title. @default false
116
+ */
117
+ enableItemLabels?: boolean;
103
118
  /** Show the uppercase extension badge on each row. @default true */
104
119
  showExtensionBadge?: boolean;
105
120
  /** Show file size on each row. @default true */
@@ -139,7 +154,7 @@ interface DocumentPickerProps {
139
154
  * onChange={(items) => setAttributes({ documents: items })}
140
155
  * />
141
156
  */
142
- declare const DocumentPicker: ({ label, hideLabelFromVision, help, variant, value: controlledValue, defaultValue, onChange, maxItems, orderable, placeholder, allowedMimeTypes, allowedExtensions, perPage, inputDelayMs, fetchOnOpen, uniqueItems, enableMediaLibrary, showExtensionBadge, showFileSize, showModifiedDate, filter, request, renderSuggestion, renderSelected, labels, className, id, }: DocumentPickerProps) => react_jsx_runtime.JSX.Element;
157
+ declare const DocumentPicker: ({ label, hideLabelFromVision, help, variant, value: controlledValue, defaultValue, onChange, maxItems, orderable, placeholder, allowedMimeTypes, allowedExtensions, perPage, inputDelayMs, fetchOnOpen, uniqueItems, enableMediaLibrary, enableItemLabels, showExtensionBadge, showFileSize, showModifiedDate, filter, request, renderSuggestion, renderSelected, labels, className, id, }: DocumentPickerProps) => react_jsx_runtime.JSX.Element;
143
158
 
144
159
  interface MediaLibraryButtonProps {
145
160
  allowedMimeTypes: string[];
@@ -23,6 +23,12 @@ interface DocumentItem {
23
23
  modified?: string;
24
24
  /** Raw REST record so consumer filters can reach beyond the normalized shape. */
25
25
  raw?: unknown;
26
+ /**
27
+ * Editor-authored display name that overrides `title` in consumer UIs.
28
+ * Never derived from the REST record — set only by the user via the
29
+ * per-item label field (see `enableItemLabels`).
30
+ */
31
+ label?: string;
26
32
  }
27
33
  /** A picked document resolved against the current entity store. */
28
34
  interface ResolvedDocumentItem extends DocumentItem {
@@ -47,6 +53,7 @@ interface DocumentPickerLabels {
47
53
  missing?: string;
48
54
  openLink?: string;
49
55
  extensionBadge?: string;
56
+ itemLabel?: string;
50
57
  }
51
58
  interface RequestContext {
52
59
  query: string;
@@ -100,6 +107,14 @@ interface DocumentPickerProps {
100
107
  uniqueItems?: boolean;
101
108
  /** Show the "Browse Media Library" button. @default true */
102
109
  enableMediaLibrary?: boolean;
110
+ /**
111
+ * Make each selected row's display name editable in place: the title is
112
+ * rendered as a text input prefilled with the effective name. Edits are
113
+ * stored on the item as `label` (the attachment itself is untouched);
114
+ * clearing the field — or typing the original title back — removes the
115
+ * override and falls back to the attachment title. @default false
116
+ */
117
+ enableItemLabels?: boolean;
103
118
  /** Show the uppercase extension badge on each row. @default true */
104
119
  showExtensionBadge?: boolean;
105
120
  /** Show file size on each row. @default true */
@@ -139,7 +154,7 @@ interface DocumentPickerProps {
139
154
  * onChange={(items) => setAttributes({ documents: items })}
140
155
  * />
141
156
  */
142
- declare const DocumentPicker: ({ label, hideLabelFromVision, help, variant, value: controlledValue, defaultValue, onChange, maxItems, orderable, placeholder, allowedMimeTypes, allowedExtensions, perPage, inputDelayMs, fetchOnOpen, uniqueItems, enableMediaLibrary, showExtensionBadge, showFileSize, showModifiedDate, filter, request, renderSuggestion, renderSelected, labels, className, id, }: DocumentPickerProps) => react_jsx_runtime.JSX.Element;
157
+ declare const DocumentPicker: ({ label, hideLabelFromVision, help, variant, value: controlledValue, defaultValue, onChange, maxItems, orderable, placeholder, allowedMimeTypes, allowedExtensions, perPage, inputDelayMs, fetchOnOpen, uniqueItems, enableMediaLibrary, enableItemLabels, showExtensionBadge, showFileSize, showModifiedDate, filter, request, renderSuggestion, renderSelected, labels, className, id, }: DocumentPickerProps) => react_jsx_runtime.JSX.Element;
143
158
 
144
159
  interface MediaLibraryButtonProps {
145
160
  allowedMimeTypes: string[];
@@ -1,6 +1,6 @@
1
1
  import { forwardRef, useId, useState, useCallback, useRef, useMemo, useEffect } from 'react';
2
2
  import { QueryClientProvider, QueryClient, useInfiniteQuery, keepPreviousData } from '@tanstack/react-query';
3
- import { VisuallyHidden, Icon, Button, Popover, Spinner } from '@wordpress/components';
3
+ import { VisuallyHidden, Icon, Button, Popover, Spinner, TextControl } from '@wordpress/components';
4
4
  import { __, sprintf, _n } from '@wordpress/i18n';
5
5
  import { search, closeSmall, upload, dragHandle, caution, chevronUp, chevronDown } from '@wordpress/icons';
6
6
  import styled from '@emotion/styled';
@@ -550,6 +550,54 @@ var DocMetaInline = styled.div`
550
550
  white-space: nowrap;
551
551
  }
552
552
  `;
553
+ var ReorderButtons = styled.span`
554
+ display: none;
555
+
556
+ @container valu-dp-row (min-width: 340px) {
557
+ display: inline-flex;
558
+ }
559
+ `;
560
+ var LabelEditBody = styled.div`
561
+ flex: 1;
562
+ min-width: 0;
563
+ display: flex;
564
+ flex-direction: column;
565
+ gap: 2px;
566
+ padding: 4px 0;
567
+
568
+ .components-base-control,
569
+ .components-text-control__input {
570
+ width: 100%;
571
+ min-width: 0;
572
+ }
573
+
574
+ /* __nextHasNoMarginBottom is not honored by every bundled
575
+ wp-components version — enforce the no-margin layout here. */
576
+ .components-base-control.components-base-control {
577
+ margin-bottom: 0;
578
+ }
579
+
580
+ /* Doubled class beats wp-components' own input rules regardless of
581
+ stylesheet order (the emotion cache may be prepended). */
582
+ .components-text-control__input.components-text-control__input {
583
+ font-size: ${tokens.fontSizeBody};
584
+ height: 28px;
585
+ min-height: 28px;
586
+ padding: 2px 8px;
587
+ }
588
+
589
+ @container valu-dp-row (min-width: ${WIDE_ROW}) {
590
+ flex-direction: row;
591
+ align-items: center;
592
+ gap: ${tokens.spaceMd};
593
+
594
+ .components-base-control {
595
+ flex: 0 1 340px;
596
+ min-width: 180px;
597
+ width: auto;
598
+ }
599
+ }
600
+ `;
553
601
  var Root2 = styled.div`
554
602
  color: ${tokens.fg};
555
603
  font-size: ${tokens.fontSizeBody};
@@ -710,6 +758,7 @@ var mapMediaToDocumentItem = (raw) => {
710
758
  raw
711
759
  };
712
760
  };
761
+ var displayTitle = (item) => item.label?.trim() || item.title;
713
762
  var dedupeAgainst = (items, value, enabled) => {
714
763
  if (!enabled || value.length === 0) return items;
715
764
  const ids = new Set(value.map((v) => v.id));
@@ -1714,6 +1763,8 @@ var SelectedItem = ({
1714
1763
  onRemove,
1715
1764
  onMoveUp,
1716
1765
  onMoveDown,
1766
+ enableLabelEdit = false,
1767
+ onLabelChange,
1717
1768
  render
1718
1769
  }) => {
1719
1770
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
@@ -1727,22 +1778,46 @@ var SelectedItem = ({
1727
1778
  };
1728
1779
  const isMissing = item.missing === "deleted";
1729
1780
  const missingLabel = labels.missing ?? __("This document is no longer available.", "valu-block-components");
1781
+ const displayName = displayTitle(item);
1730
1782
  const removeLabel = labels.remove ? labels.remove : sprintf(
1731
1783
  /* translators: %s: document title */
1732
1784
  __("Remove %s", "valu-block-components"),
1733
- item.title
1785
+ displayName
1734
1786
  );
1735
1787
  const moveUpLabel = labels.moveUp ? labels.moveUp : sprintf(
1736
1788
  /* translators: %s: document title */
1737
1789
  __("Move %s up", "valu-block-components"),
1738
- item.title
1790
+ displayName
1739
1791
  );
1740
1792
  const moveDownLabel = labels.moveDown ? labels.moveDown : sprintf(
1741
1793
  /* translators: %s: document title */
1742
1794
  __("Move %s down", "valu-block-components"),
1743
- item.title
1795
+ displayName
1744
1796
  );
1745
1797
  const modified = showModifiedDate ? formatModified(item.modified) : "";
1798
+ const isLabelEdit = enableLabelEdit && !isMissing && Boolean(onLabelChange);
1799
+ const [labelDraft, setLabelDraft] = useState(null);
1800
+ const commitTimer = useRef(null);
1801
+ useEffect(
1802
+ () => () => {
1803
+ if (commitTimer.current) {
1804
+ clearTimeout(commitTimer.current);
1805
+ }
1806
+ },
1807
+ []
1808
+ );
1809
+ const normalizeLabel = (value) => {
1810
+ const trimmed = value.trim();
1811
+ return trimmed === "" || trimmed === item.title ? "" : value;
1812
+ };
1813
+ const itemLabelText = labels.itemLabel ?? __("Display name", "valu-block-components");
1814
+ const metaParts = [item.filename];
1815
+ if (showFileSize && item.fileSize) {
1816
+ metaParts.push(formatFileSize(item.fileSize));
1817
+ }
1818
+ if (modified) {
1819
+ metaParts.push(modified);
1820
+ }
1746
1821
  return /* @__PURE__ */ jsxs(
1747
1822
  SelectedRow2,
1748
1823
  {
@@ -1764,9 +1839,45 @@ var SelectedItem = ({
1764
1839
  }
1765
1840
  ) : null,
1766
1841
  isMissing ? /* @__PURE__ */ jsx(Icon, { icon: caution, size: 20 }) : showExtensionBadge ? /* @__PURE__ */ jsx(ExtensionBadge, { extension: item.extension }) : null,
1767
- render ? render(item) : /* @__PURE__ */ jsxs(Fragment, { children: [
1842
+ render ? render(item) : isLabelEdit ? /* @__PURE__ */ jsxs(LabelEditBody, { children: [
1843
+ /* @__PURE__ */ jsx(
1844
+ TextControl,
1845
+ {
1846
+ __nextHasNoMarginBottom: true,
1847
+ __next40pxDefaultSize: false,
1848
+ "aria-label": sprintf(
1849
+ /* translators: 1: display-name field label, 2: filename */
1850
+ "%1$s: %2$s",
1851
+ itemLabelText,
1852
+ item.filename
1853
+ ),
1854
+ value: labelDraft ?? item.label ?? item.title,
1855
+ onChange: (value) => {
1856
+ setLabelDraft(value);
1857
+ if (commitTimer.current) {
1858
+ clearTimeout(commitTimer.current);
1859
+ }
1860
+ commitTimer.current = setTimeout(() => {
1861
+ commitTimer.current = null;
1862
+ onLabelChange?.(normalizeLabel(value));
1863
+ }, 300);
1864
+ },
1865
+ onBlur: () => {
1866
+ if (commitTimer.current) {
1867
+ clearTimeout(commitTimer.current);
1868
+ commitTimer.current = null;
1869
+ if (labelDraft !== null) {
1870
+ onLabelChange?.(normalizeLabel(labelDraft));
1871
+ }
1872
+ }
1873
+ setLabelDraft(null);
1874
+ }
1875
+ }
1876
+ ),
1877
+ /* @__PURE__ */ jsx(FilenameText, { title: item.filename, children: metaParts.join(" \xB7 ") })
1878
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1768
1879
  /* @__PURE__ */ jsxs(DocRowBody, { children: [
1769
- /* @__PURE__ */ jsx(PrimaryText, { muted: isMissing, title: item.title, children: isMissing ? missingLabel : item.title }),
1880
+ /* @__PURE__ */ jsx(PrimaryText, { muted: isMissing, title: displayName, children: isMissing ? missingLabel : displayName }),
1770
1881
  !isMissing ? /* @__PURE__ */ jsx(FilenameText, { title: item.filename, children: item.filename }) : null
1771
1882
  ] }),
1772
1883
  !isMissing && (showFileSize || modified) ? /* @__PURE__ */ jsxs(DocMetaInline, { children: [
@@ -1775,7 +1886,7 @@ var SelectedItem = ({
1775
1886
  ] }) : null
1776
1887
  ] }),
1777
1888
  /* @__PURE__ */ jsxs(ActionColumn, { children: [
1778
- orderable ? /* @__PURE__ */ jsxs(Fragment, { children: [
1889
+ orderable ? /* @__PURE__ */ jsxs(ReorderButtons, { children: [
1779
1890
  /* @__PURE__ */ jsx(
1780
1891
  Button,
1781
1892
  {
@@ -1871,7 +1982,8 @@ var SelectedList = ({
1871
1982
  showHeader = true,
1872
1983
  showExtensionBadge,
1873
1984
  showFileSize,
1874
- showModifiedDate
1985
+ showModifiedDate,
1986
+ enableItemLabels = false
1875
1987
  }) => {
1876
1988
  const resolved = useResolvedDocuments(value);
1877
1989
  const [activeKey, setActiveKey] = useState(null);
@@ -1886,6 +1998,22 @@ var SelectedList = ({
1886
1998
  (key) => onChange(value.filter((i) => i.key !== key)),
1887
1999
  [value, onChange]
1888
2000
  );
2001
+ const setLabel = useCallback(
2002
+ (key, label) => onChange(
2003
+ value.map((i) => {
2004
+ if (i.key !== key) {
2005
+ return i;
2006
+ }
2007
+ if (label === "") {
2008
+ const rest = { ...i };
2009
+ delete rest.label;
2010
+ return rest;
2011
+ }
2012
+ return { ...i, label };
2013
+ })
2014
+ ),
2015
+ [value, onChange]
2016
+ );
1889
2017
  const reorder = useCallback(
1890
2018
  (from, to) => {
1891
2019
  if (from === to) return;
@@ -1934,6 +2062,10 @@ var SelectedList = ({
1934
2062
  onRemove: () => remove(item.key),
1935
2063
  ...index > 0 ? { onMoveUp: () => reorder(index, index - 1) } : {},
1936
2064
  ...index < resolved.length - 1 ? { onMoveDown: () => reorder(index, index + 1) } : {},
2065
+ ...enableItemLabels ? {
2066
+ enableLabelEdit: true,
2067
+ onLabelChange: (label) => setLabel(item.key, label)
2068
+ } : {},
1937
2069
  ...renderSelected ? { render: renderSelected } : {}
1938
2070
  },
1939
2071
  item.key
@@ -1956,7 +2088,7 @@ var SelectedList = ({
1956
2088
  onDragCancel: () => setActiveKey(null),
1957
2089
  children: [
1958
2090
  /* @__PURE__ */ jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx(SelectedListEl, { role: "list", children: rows }) }),
1959
- /* @__PURE__ */ jsx(DragOverlay, { dropAnimation: defaultDropAnimation, children: activeItem ? /* @__PURE__ */ jsx(DragChip, { title: activeItem.title }) : null })
2091
+ /* @__PURE__ */ jsx(DragOverlay, { dropAnimation: defaultDropAnimation, children: activeItem ? /* @__PURE__ */ jsx(DragChip, { title: displayTitle(activeItem) }) : null })
1960
2092
  ]
1961
2093
  }
1962
2094
  )
@@ -1987,6 +2119,7 @@ var DocumentPicker = ({
1987
2119
  fetchOnOpen = false,
1988
2120
  uniqueItems = true,
1989
2121
  enableMediaLibrary = true,
2122
+ enableItemLabels = false,
1990
2123
  showExtensionBadge = true,
1991
2124
  showFileSize = true,
1992
2125
  showModifiedDate = false,
@@ -2078,7 +2211,14 @@ var DocumentPicker = ({
2078
2211
  ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
2079
2212
  multiple: false,
2080
2213
  value,
2081
- onSelect: (items) => handleChange([items[items.length - 1]]),
2214
+ onSelect: (items) => {
2215
+ const next = items[items.length - 1];
2216
+ const prev = value[0];
2217
+ const keepLabel = prev?.id === next.id && prev.label !== void 0;
2218
+ handleChange([
2219
+ keepLabel ? { ...next, label: prev.label } : next
2220
+ ]);
2221
+ },
2082
2222
  labels,
2083
2223
  variant: "tertiary",
2084
2224
  label: labels.replace
@@ -2096,7 +2236,8 @@ var DocumentPicker = ({
2096
2236
  showHeader: value.length > 0,
2097
2237
  showExtensionBadge,
2098
2238
  showFileSize,
2099
- showModifiedDate
2239
+ showModifiedDate,
2240
+ enableItemLabels
2100
2241
  }
2101
2242
  )
2102
2243
  ] }) }) });