@valu/block-components 0.3.1 → 0.5.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
  {
@@ -1831,7 +1942,7 @@ var useResolvedDocuments = (value) => {
1831
1942
  "attachment",
1832
1943
  item.id
1833
1944
  ]);
1834
- out[item.id] = { record: record ?? null, resolved };
1945
+ out[item.id] = resolved ? record ?? null : void 0;
1835
1946
  }
1836
1947
  return out;
1837
1948
  },
@@ -1839,10 +1950,8 @@ var useResolvedDocuments = (value) => {
1839
1950
  );
1840
1951
  return useMemo(
1841
1952
  () => value.map((item) => {
1842
- const entry = records[item.id];
1843
- if (!entry) return item;
1844
- const { record, resolved } = entry;
1845
- if (!resolved) return item;
1953
+ const record = records[item.id];
1954
+ if (record === void 0) return item;
1846
1955
  if (!record) return { ...item, missing: "deleted" };
1847
1956
  const fresh = mapMediaToDocumentItem(
1848
1957
  record
@@ -1871,7 +1980,8 @@ var SelectedList = ({
1871
1980
  showHeader = true,
1872
1981
  showExtensionBadge,
1873
1982
  showFileSize,
1874
- showModifiedDate
1983
+ showModifiedDate,
1984
+ enableItemLabels = false
1875
1985
  }) => {
1876
1986
  const resolved = useResolvedDocuments(value);
1877
1987
  const [activeKey, setActiveKey] = useState(null);
@@ -1886,6 +1996,22 @@ var SelectedList = ({
1886
1996
  (key) => onChange(value.filter((i) => i.key !== key)),
1887
1997
  [value, onChange]
1888
1998
  );
1999
+ const setLabel = useCallback(
2000
+ (key, label) => onChange(
2001
+ value.map((i) => {
2002
+ if (i.key !== key) {
2003
+ return i;
2004
+ }
2005
+ if (label === "") {
2006
+ const rest = { ...i };
2007
+ delete rest.label;
2008
+ return rest;
2009
+ }
2010
+ return { ...i, label };
2011
+ })
2012
+ ),
2013
+ [value, onChange]
2014
+ );
1889
2015
  const reorder = useCallback(
1890
2016
  (from, to) => {
1891
2017
  if (from === to) return;
@@ -1934,6 +2060,10 @@ var SelectedList = ({
1934
2060
  onRemove: () => remove(item.key),
1935
2061
  ...index > 0 ? { onMoveUp: () => reorder(index, index - 1) } : {},
1936
2062
  ...index < resolved.length - 1 ? { onMoveDown: () => reorder(index, index + 1) } : {},
2063
+ ...enableItemLabels ? {
2064
+ enableLabelEdit: true,
2065
+ onLabelChange: (label) => setLabel(item.key, label)
2066
+ } : {},
1937
2067
  ...renderSelected ? { render: renderSelected } : {}
1938
2068
  },
1939
2069
  item.key
@@ -1956,7 +2086,7 @@ var SelectedList = ({
1956
2086
  onDragCancel: () => setActiveKey(null),
1957
2087
  children: [
1958
2088
  /* @__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 })
2089
+ /* @__PURE__ */ jsx(DragOverlay, { dropAnimation: defaultDropAnimation, children: activeItem ? /* @__PURE__ */ jsx(DragChip, { title: displayTitle(activeItem) }) : null })
1960
2090
  ]
1961
2091
  }
1962
2092
  )
@@ -1987,6 +2117,7 @@ var DocumentPicker = ({
1987
2117
  fetchOnOpen = false,
1988
2118
  uniqueItems = true,
1989
2119
  enableMediaLibrary = true,
2120
+ enableItemLabels = false,
1990
2121
  showExtensionBadge = true,
1991
2122
  showFileSize = true,
1992
2123
  showModifiedDate = false,
@@ -2078,7 +2209,14 @@ var DocumentPicker = ({
2078
2209
  ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
2079
2210
  multiple: false,
2080
2211
  value,
2081
- onSelect: (items) => handleChange([items[items.length - 1]]),
2212
+ onSelect: (items) => {
2213
+ const next = items[items.length - 1];
2214
+ const prev = value[0];
2215
+ const keepLabel = prev?.id === next.id && prev.label !== void 0;
2216
+ handleChange([
2217
+ keepLabel ? { ...next, label: prev.label } : next
2218
+ ]);
2219
+ },
2082
2220
  labels,
2083
2221
  variant: "tertiary",
2084
2222
  label: labels.replace
@@ -2096,7 +2234,8 @@ var DocumentPicker = ({
2096
2234
  showHeader: value.length > 0,
2097
2235
  showExtensionBadge,
2098
2236
  showFileSize,
2099
- showModifiedDate
2237
+ showModifiedDate,
2238
+ enableItemLabels
2100
2239
  }
2101
2240
  )
2102
2241
  ] }) }) });