@wix/patterns-fields 1.13.0 → 1.15.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 (192) hide show
  1. package/dist/cjs/assets/locale/messages_en.json +7 -1
  2. package/dist/cjs/components/address/input/address-input.st.css.js +3 -3
  3. package/dist/cjs/components/address/input/address-input.st.css.js.map +1 -1
  4. package/dist/cjs/components/audio/actions/actions-menu/actions-menu.st.css.js +3 -3
  5. package/dist/cjs/components/audio/actions/actions-menu/actions-menu.st.css.js.map +1 -1
  6. package/dist/cjs/components/audio/audio-field/form-audio-field.st.css.js +4 -4
  7. package/dist/cjs/components/audio/audio-field/form-audio-field.st.css.js.map +1 -1
  8. package/dist/cjs/components/audio/audio-player/audio-player.st.css.js +2 -2
  9. package/dist/cjs/components/audio/audio-player/audio-player.st.css.js.map +1 -1
  10. package/dist/cjs/components/color-view/color-view.st.css.js +4 -4
  11. package/dist/cjs/components/color-view/color-view.st.css.js.map +1 -1
  12. package/dist/cjs/components/delete-dialog/delete-dialog.st.css.js +3 -3
  13. package/dist/cjs/components/delete-dialog/delete-dialog.st.css.js.map +1 -1
  14. package/dist/cjs/components/document/form-document-field.st.css.js +7 -7
  15. package/dist/cjs/components/document/form-document-field.st.css.js.map +1 -1
  16. package/dist/cjs/components/exclamation/exclamation.st.css.js +5 -5
  17. package/dist/cjs/components/exclamation/exclamation.st.css.js.map +1 -1
  18. package/dist/cjs/components/highlighted-text/highlighted-text.st.css.js +5 -5
  19. package/dist/cjs/components/highlighted-text/highlighted-text.st.css.js.map +1 -1
  20. package/dist/cjs/components/image/cell-image-edit.js +474 -0
  21. package/dist/cjs/components/image/cell-image-edit.js.map +1 -0
  22. package/dist/cjs/components/image/image-thumbnail.js +78 -0
  23. package/dist/cjs/components/image/image-thumbnail.js.map +1 -0
  24. package/dist/cjs/components/image/image-view.js +123 -0
  25. package/dist/cjs/components/image/image-view.js.map +1 -0
  26. package/dist/cjs/components/image/index.js +22 -0
  27. package/dist/cjs/components/image/index.js.map +1 -0
  28. package/dist/cjs/components/index.js +6 -0
  29. package/dist/cjs/components/index.js.map +1 -1
  30. package/dist/cjs/components/media-control/paste-url-button.st.css.js +4 -4
  31. package/dist/cjs/components/media-control/paste-url-button.st.css.js.map +1 -1
  32. package/dist/cjs/components/media-gallery/form-media-gallery-field.st.css.js +10 -10
  33. package/dist/cjs/components/media-gallery/form-media-gallery-field.st.css.js.map +1 -1
  34. package/dist/cjs/components/media-image/media-image.st.css.js +4 -4
  35. package/dist/cjs/components/media-image/media-image.st.css.js.map +1 -1
  36. package/dist/cjs/components/media-image/overlays/custom-url-overlay.js +32 -0
  37. package/dist/cjs/components/media-image/overlays/custom-url-overlay.js.map +1 -0
  38. package/dist/cjs/components/media-image/overlays/index.js +3 -1
  39. package/dist/cjs/components/media-image/overlays/index.js.map +1 -1
  40. package/dist/cjs/components/media-image/overlays/overlay.st.css.js +4 -4
  41. package/dist/cjs/components/media-image/overlays/overlay.st.css.js.map +1 -1
  42. package/dist/cjs/components/media-image/overlays/types.js.map +1 -1
  43. package/dist/cjs/components/media-loader/media-loader.st.css.js +3 -3
  44. package/dist/cjs/components/media-loader/media-loader.st.css.js.map +1 -1
  45. package/dist/cjs/components/media-tag/web-media-tag/media-tag.st.css.js +2 -2
  46. package/dist/cjs/components/media-tag/web-media-tag/media-tag.st.css.js.map +1 -1
  47. package/dist/cjs/components/multi-document/multi-document-input/form-multi-document.st.css.js +5 -5
  48. package/dist/cjs/components/multi-document/multi-document-input/form-multi-document.st.css.js.map +1 -1
  49. package/dist/cjs/components/rich-content/rich-content-input/default-value-input/rich-content-default-value-input.st.css.js +6 -6
  50. package/dist/cjs/components/rich-content/rich-content-input/default-value-input/rich-content-default-value-input.st.css.js.map +1 -1
  51. package/dist/cjs/components/rich-content/rich-content-input/form-input/rich-content-form-input.st.css.js +5 -5
  52. package/dist/cjs/components/rich-content/rich-content-input/form-input/rich-content-form-input.st.css.js.map +1 -1
  53. package/dist/cjs/components/rich-content/rich-content-input/form-read-only-input/rich-content-form-read-only-input.st.css.js +2 -2
  54. package/dist/cjs/components/rich-content/rich-content-input/form-read-only-input/rich-content-form-read-only-input.st.css.js.map +1 -1
  55. package/dist/cjs/components/rich-content/rich-content-input/rich-content-common/fullscreen-modal/fullscreen-modal.st.css.js +3 -3
  56. package/dist/cjs/components/rich-content/rich-content-input/rich-content-common/fullscreen-modal/fullscreen-modal.st.css.js.map +1 -1
  57. package/dist/cjs/components/rich-content/rich-content-input/rich-content-common/publish-loader/publish-loader.st.css.js +3 -3
  58. package/dist/cjs/components/rich-content/rich-content-input/rich-content-common/publish-loader/publish-loader.st.css.js.map +1 -1
  59. package/dist/cjs/components/rich-content/rich-content-input/rich-content-common/toggle-fullscreen-button/toggle-fullscreen-button.st.css.js +2 -2
  60. package/dist/cjs/components/rich-content/rich-content-input/rich-content-common/toggle-fullscreen-button/toggle-fullscreen-button.st.css.js.map +1 -1
  61. package/dist/cjs/components/rich-content/rich-content-input/rich-content-editor/rich-content-editor.st.css.js +9 -9
  62. package/dist/cjs/components/rich-content/rich-content-input/rich-content-editor/rich-content-editor.st.css.js.map +1 -1
  63. package/dist/cjs/components/rich-content/rich-content-input/rich-content-editor/rich-content-toolbar.st.css.js +4 -4
  64. package/dist/cjs/components/rich-content/rich-content-input/rich-content-editor/rich-content-toolbar.st.css.js.map +1 -1
  65. package/dist/cjs/components/rich-content/rich-content-input/rich-content-viewer/rich-content-viewer.st.css.js +4 -4
  66. package/dist/cjs/components/rich-content/rich-content-input/rich-content-viewer/rich-content-viewer.st.css.js.map +1 -1
  67. package/dist/cjs/components/rich-text/rich-text-input/rich-text-editor/rich-editor.st.css.js +18 -18
  68. package/dist/cjs/components/rich-text/rich-text-input/rich-text-editor/rich-editor.st.css.js.map +1 -1
  69. package/dist/cjs/components/rich-text/rich-text-input/rich-text-editor/rich-text-editor.st.css.js +4 -4
  70. package/dist/cjs/components/rich-text/rich-text-input/rich-text-editor/rich-text-editor.st.css.js.map +1 -1
  71. package/dist/cjs/components/rich-text/rich-text-input/rich-text-editor/toolbar/toolbar.st.css.js +18 -18
  72. package/dist/cjs/components/rich-text/rich-text-input/rich-text-editor/toolbar/toolbar.st.css.js.map +1 -1
  73. package/dist/cjs/components/text/text-view.st.css.js +3 -3
  74. package/dist/cjs/components/text/text-view.st.css.js.map +1 -1
  75. package/dist/cjs/services/translations.js.map +1 -1
  76. package/dist/cjs/styles.global.css +1 -1
  77. package/dist/esm/assets/locale/messages_en.json +7 -1
  78. package/dist/esm/components/address/input/address-input.st.css.js +2 -2
  79. package/dist/esm/components/audio/actions/actions-menu/actions-menu.st.css.js +2 -2
  80. package/dist/esm/components/audio/audio-field/form-audio-field.st.css.js +2 -2
  81. package/dist/esm/components/audio/audio-player/audio-player.st.css.js +2 -2
  82. package/dist/esm/components/color-view/color-view.st.css.js +2 -2
  83. package/dist/esm/components/delete-dialog/delete-dialog.st.css.js +2 -2
  84. package/dist/esm/components/delete-dialog/delete-dialog.st.css.js.map +1 -1
  85. package/dist/esm/components/document/form-document-field.st.css.js +2 -2
  86. package/dist/esm/components/exclamation/exclamation.st.css.js +2 -2
  87. package/dist/esm/components/highlighted-text/highlighted-text.st.css.js +3 -3
  88. package/dist/esm/components/highlighted-text/highlighted-text.st.css.js.map +1 -1
  89. package/dist/esm/components/image/cell-image-edit.js +188 -0
  90. package/dist/esm/components/image/cell-image-edit.js.map +1 -0
  91. package/dist/esm/components/image/image-thumbnail.js +11 -0
  92. package/dist/esm/components/image/image-thumbnail.js.map +1 -0
  93. package/dist/esm/components/image/image-view.js +19 -0
  94. package/dist/esm/components/image/image-view.js.map +1 -0
  95. package/dist/esm/components/image/index.js +4 -0
  96. package/dist/esm/components/image/index.js.map +1 -0
  97. package/dist/esm/components/index.js +1 -0
  98. package/dist/esm/components/index.js.map +1 -1
  99. package/dist/esm/components/media-control/paste-url-button.st.css.js +2 -2
  100. package/dist/esm/components/media-gallery/form-media-gallery-field.st.css.js +2 -2
  101. package/dist/esm/components/media-image/media-image.st.css.js +3 -3
  102. package/dist/esm/components/media-image/overlays/custom-url-overlay.js +10 -0
  103. package/dist/esm/components/media-image/overlays/custom-url-overlay.js.map +1 -0
  104. package/dist/esm/components/media-image/overlays/index.js +1 -0
  105. package/dist/esm/components/media-image/overlays/index.js.map +1 -1
  106. package/dist/esm/components/media-image/overlays/overlay.st.css.js +3 -3
  107. package/dist/esm/components/media-image/overlays/overlay.st.css.js.map +1 -1
  108. package/dist/esm/components/media-loader/media-loader.st.css.js +2 -2
  109. package/dist/esm/components/media-tag/web-media-tag/media-tag.st.css.js +2 -2
  110. package/dist/esm/components/multi-document/multi-document-input/form-multi-document.st.css.js +2 -2
  111. package/dist/esm/components/rich-content/rich-content-input/default-value-input/rich-content-default-value-input.st.css.js +3 -3
  112. package/dist/esm/components/rich-content/rich-content-input/default-value-input/rich-content-default-value-input.st.css.js.map +1 -1
  113. package/dist/esm/components/rich-content/rich-content-input/form-input/rich-content-form-input.st.css.js +3 -3
  114. package/dist/esm/components/rich-content/rich-content-input/form-read-only-input/rich-content-form-read-only-input.st.css.js +2 -2
  115. package/dist/esm/components/rich-content/rich-content-input/rich-content-common/fullscreen-modal/fullscreen-modal.st.css.js +2 -2
  116. package/dist/esm/components/rich-content/rich-content-input/rich-content-common/fullscreen-modal/fullscreen-modal.st.css.js.map +1 -1
  117. package/dist/esm/components/rich-content/rich-content-input/rich-content-common/publish-loader/publish-loader.st.css.js +2 -2
  118. package/dist/esm/components/rich-content/rich-content-input/rich-content-common/toggle-fullscreen-button/toggle-fullscreen-button.st.css.js +2 -2
  119. package/dist/esm/components/rich-content/rich-content-input/rich-content-editor/rich-content-editor.st.css.js +3 -3
  120. package/dist/esm/components/rich-content/rich-content-input/rich-content-editor/rich-content-toolbar.st.css.js +2 -2
  121. package/dist/esm/components/rich-content/rich-content-input/rich-content-viewer/rich-content-viewer.st.css.js +2 -2
  122. package/dist/esm/components/rich-text/rich-text-input/rich-text-editor/rich-editor.st.css.js +2 -2
  123. package/dist/esm/components/rich-text/rich-text-input/rich-text-editor/rich-editor.st.css.js.map +1 -1
  124. package/dist/esm/components/rich-text/rich-text-input/rich-text-editor/rich-text-editor.st.css.js +2 -2
  125. package/dist/esm/components/rich-text/rich-text-input/rich-text-editor/toolbar/toolbar.st.css.js +2 -2
  126. package/dist/esm/components/text/text-view.st.css.js +2 -2
  127. package/dist/esm/styles.global.css +1 -1
  128. package/dist/types/components/delete-dialog/delete-dialog.st.css.d.ts.map +1 -1
  129. package/dist/types/components/highlighted-text/highlighted-text.st.css.d.ts.map +1 -1
  130. package/dist/types/components/image/cell-image-edit.d.ts +18 -0
  131. package/dist/types/components/image/cell-image-edit.d.ts.map +1 -0
  132. package/dist/types/components/image/image-thumbnail.d.ts +11 -0
  133. package/dist/types/components/image/image-thumbnail.d.ts.map +1 -0
  134. package/dist/types/components/image/image-view.d.ts +7 -0
  135. package/dist/types/components/image/image-view.d.ts.map +1 -0
  136. package/dist/types/components/image/index.d.ts +4 -0
  137. package/dist/types/components/image/index.d.ts.map +1 -0
  138. package/dist/types/components/index.d.ts +1 -0
  139. package/dist/types/components/index.d.ts.map +1 -1
  140. package/dist/types/components/media-image/overlays/custom-url-overlay.d.ts +5 -0
  141. package/dist/types/components/media-image/overlays/custom-url-overlay.d.ts.map +1 -0
  142. package/dist/types/components/media-image/overlays/index.d.ts +1 -0
  143. package/dist/types/components/media-image/overlays/index.d.ts.map +1 -1
  144. package/dist/types/components/media-image/overlays/overlay.st.css.d.ts.map +1 -1
  145. package/dist/types/components/media-image/overlays/types.d.ts +1 -1
  146. package/dist/types/components/media-image/overlays/types.d.ts.map +1 -1
  147. package/dist/types/components/rich-content/rich-content-input/default-value-input/rich-content-default-value-input.st.css.d.ts.map +1 -1
  148. package/dist/types/components/rich-content/rich-content-input/rich-content-common/fullscreen-modal/fullscreen-modal.st.css.d.ts.map +1 -1
  149. package/dist/types/components/rich-text/rich-text-input/rich-text-editor/rich-editor.st.css.d.ts.map +1 -1
  150. package/dist/types/services/translations.d.ts +1 -1
  151. package/dist/types/services/translations.d.ts.map +1 -1
  152. package/package.json +4 -4
  153. package/src/assets/locale/messages_en.json +7 -1
  154. package/src/components/address/input/address-input.st.css.ts +2 -2
  155. package/src/components/audio/actions/actions-menu/actions-menu.st.css.ts +2 -2
  156. package/src/components/audio/audio-field/form-audio-field.st.css.ts +2 -2
  157. package/src/components/audio/audio-player/audio-player.st.css.ts +2 -2
  158. package/src/components/color-view/color-view.st.css.ts +2 -2
  159. package/src/components/delete-dialog/delete-dialog.st.css.ts +2 -2
  160. package/src/components/document/form-document-field.st.css.ts +2 -2
  161. package/src/components/exclamation/exclamation.st.css.ts +2 -2
  162. package/src/components/highlighted-text/highlighted-text.st.css.ts +3 -3
  163. package/src/components/image/cell-image-edit.tsx +353 -0
  164. package/src/components/image/image-thumbnail.tsx +63 -0
  165. package/src/components/image/image-view.tsx +69 -0
  166. package/src/components/image/index.ts +3 -0
  167. package/src/components/index.ts +1 -0
  168. package/src/components/media-control/paste-url-button.st.css.ts +2 -2
  169. package/src/components/media-gallery/form-media-gallery-field.st.css.ts +2 -2
  170. package/src/components/media-image/media-image.st.css.ts +3 -3
  171. package/src/components/media-image/overlays/custom-url-overlay.tsx +18 -0
  172. package/src/components/media-image/overlays/index.ts +1 -0
  173. package/src/components/media-image/overlays/overlay.st.css.ts +3 -3
  174. package/src/components/media-image/overlays/types.ts +1 -1
  175. package/src/components/media-loader/media-loader.st.css.ts +2 -2
  176. package/src/components/media-tag/web-media-tag/media-tag.st.css.ts +2 -2
  177. package/src/components/multi-document/multi-document-input/form-multi-document.st.css.ts +2 -2
  178. package/src/components/rich-content/rich-content-input/default-value-input/rich-content-default-value-input.st.css.ts +3 -3
  179. package/src/components/rich-content/rich-content-input/form-input/rich-content-form-input.st.css.ts +3 -3
  180. package/src/components/rich-content/rich-content-input/form-read-only-input/rich-content-form-read-only-input.st.css.ts +2 -2
  181. package/src/components/rich-content/rich-content-input/rich-content-common/fullscreen-modal/fullscreen-modal.st.css.ts +2 -2
  182. package/src/components/rich-content/rich-content-input/rich-content-common/publish-loader/publish-loader.st.css.ts +2 -2
  183. package/src/components/rich-content/rich-content-input/rich-content-common/toggle-fullscreen-button/toggle-fullscreen-button.st.css.ts +2 -2
  184. package/src/components/rich-content/rich-content-input/rich-content-editor/rich-content-editor.st.css.ts +3 -3
  185. package/src/components/rich-content/rich-content-input/rich-content-editor/rich-content-toolbar.st.css.ts +2 -2
  186. package/src/components/rich-content/rich-content-input/rich-content-viewer/rich-content-viewer.st.css.ts +2 -2
  187. package/src/components/rich-text/rich-text-input/rich-text-editor/rich-editor.st.css.ts +2 -2
  188. package/src/components/rich-text/rich-text-input/rich-text-editor/rich-text-editor.st.css.ts +2 -2
  189. package/src/components/rich-text/rich-text-input/rich-text-editor/toolbar/toolbar.st.css.ts +2 -2
  190. package/src/components/text/text-view.st.css.ts +2 -2
  191. package/src/services/translations.ts +6 -0
  192. package/src/styles.global.css +1 -1
@@ -0,0 +1,353 @@
1
+ import React, { useCallback, useEffect, useRef } from 'react';
2
+ import {
3
+ Box,
4
+ IconButton,
5
+ Loader,
6
+ PopoverMenu,
7
+ Tooltip,
8
+ } from '@wix/design-system';
9
+ import {
10
+ Add,
11
+ LinkSmall,
12
+ MoreSmall,
13
+ Replace,
14
+ Link,
15
+ Delete,
16
+ } from '@wix/wix-ui-icons-common';
17
+ import { MEDIA_TYPES } from '../../utils/media-utils';
18
+ import { useTranslations } from '../../hooks';
19
+ import {
20
+ useMediaControl,
21
+ usePasteURLForm,
22
+ PasteURLForm,
23
+ MediaFieldType,
24
+ } from '../media-control';
25
+ import { ImageThumbnail } from './image-thumbnail';
26
+
27
+ export interface CellImageEditProps {
28
+ value: string | null | undefined;
29
+ onChange: (value: string | null) => void;
30
+ onCommit: () => void;
31
+ onCancel?: () => void;
32
+ /**
33
+ * Called before opening external UI (media manager / paste-URL form) so the
34
+ * table can set isEditing=true and protect cell focus while the modal is open.
35
+ */
36
+ onStartEdit?: () => void;
37
+ inputRef?: React.MutableRefObject<{ focus: () => void } | null | undefined>;
38
+ dataHook?: string;
39
+ }
40
+
41
+ export const CellImageEdit: React.FC<CellImageEditProps> = ({
42
+ value,
43
+ onChange,
44
+ onCommit,
45
+ onCancel,
46
+ onStartEdit,
47
+ inputRef,
48
+ dataHook,
49
+ }) => {
50
+ const { t } = useTranslations();
51
+ const containerRef = useRef<HTMLDivElement | null>(null);
52
+ // Tracks whether onMediaChange fired during the current callMedia() invocation.
53
+ // Reset to false before each call; checked after the promise resolves to detect
54
+ // "dismissed without selection" so the in-progress edit can be cancelled.
55
+ const mediaCommittedRef = useRef(false);
56
+
57
+ const onMediaChange = useCallback(
58
+ (uri: string) => {
59
+ mediaCommittedRef.current = true;
60
+ onChange(uri);
61
+ onCommit();
62
+ },
63
+ [onChange, onCommit],
64
+ );
65
+
66
+ const { callMedia, loading } = useMediaControl({
67
+ mediaType: MEDIA_TYPES.IMAGE,
68
+ value: value ?? undefined,
69
+ onChange: onMediaChange,
70
+ });
71
+
72
+ const handleCallMedia = useCallback(async () => {
73
+ onStartEdit?.();
74
+ mediaCommittedRef.current = false;
75
+ await callMedia();
76
+ // If the picker was dismissed without a selection, neither onCommit nor
77
+ // onCancel ran — call onCancel now so isEditing resets to false and
78
+ // useClearFocusOnBlur is unblocked.
79
+ if (!mediaCommittedRef.current) {
80
+ onCancel?.();
81
+ }
82
+ }, [onStartEdit, callMedia, onCancel]);
83
+
84
+ const pasteUrlTitle = t('CMS.image.replaceWithUrl.title');
85
+ const { isOpen, title, onOpen, onClose, onSave } = usePasteURLForm({
86
+ onChange: onMediaChange,
87
+ });
88
+
89
+ // When the paste-URL form is dismissed without saving, cancel the in-progress
90
+ // edit so isEditing returns to false and useClearFocusOnBlur works normally.
91
+ const handleFormClose = useCallback(() => {
92
+ onClose();
93
+ onCancel?.();
94
+ }, [onClose, onCancel]);
95
+
96
+ const handleOpenPasteUrl = useCallback(
97
+ (params: { title: string; value?: string }) => () => {
98
+ onStartEdit?.();
99
+ onOpen(params)();
100
+ },
101
+ [onStartEdit, onOpen],
102
+ );
103
+
104
+ const handleDelete = useCallback(() => {
105
+ onChange(null);
106
+ onCommit();
107
+ }, [onChange, onCommit]);
108
+
109
+ // Wire inputRef so the cell-level useEffect can focus the first button when
110
+ // the user presses Enter on a focused image cell (inner focus activation).
111
+ useEffect(() => {
112
+ if (!inputRef) return;
113
+ inputRef.current = {
114
+ focus: () => {
115
+ const first =
116
+ containerRef.current?.querySelectorAll<HTMLElement>('button')[0];
117
+ first?.focus();
118
+ },
119
+ };
120
+ return () => {
121
+ inputRef.current = null;
122
+ };
123
+ }, [inputRef]);
124
+
125
+ const handleContainerFocus = useCallback(() => {
126
+ containerRef.current?.setAttribute('data-inner-focused', '');
127
+ }, []);
128
+
129
+ const handleContainerBlur = useCallback((e: React.FocusEvent) => {
130
+ if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {
131
+ containerRef.current?.removeAttribute('data-inner-focused');
132
+ }
133
+ }, []);
134
+
135
+ const focusCell = useCallback(() => {
136
+ const cellDiv = containerRef.current?.closest(
137
+ '[role="gridcell"]',
138
+ ) as HTMLElement | null;
139
+ cellDiv?.focus({ preventScroll: true });
140
+ }, []);
141
+
142
+ // Native keydown listener — must be native (not React onKeyDown) so that
143
+ // e.stopPropagation() fires during the native bubble phase, before the event
144
+ // reaches the React root where useKeyboardNavigation's onKeyDown would
145
+ // otherwise intercept Tab and move to the next cell.
146
+ useEffect(() => {
147
+ const el = containerRef.current;
148
+ if (!el) return;
149
+
150
+ const handleKeyDown = (e: KeyboardEvent) => {
151
+ if (e.key === 'Escape') {
152
+ e.preventDefault();
153
+ e.stopPropagation();
154
+ // Cancel any in-progress edit (e.g. if isEditing was set before opening
155
+ // a modal that then got dismissed via keyboard), then return focus to the
156
+ // cell div. The useCellFocusAndEditing useEffect will also refocus the
157
+ // cell div if isEditing transitions false→false is a no-op, so we also
158
+ // call focusCell() directly as a reliable fallback.
159
+ onCancel?.();
160
+ focusCell();
161
+ return;
162
+ }
163
+
164
+ // Tab while a button in this component is focused: cycle through the
165
+ // interactive elements. Tab off the last (or Shift+Tab off the first)
166
+ // returns focus to the cell div for normal cell-level navigation.
167
+ if (e.key === 'Tab') {
168
+ const buttons = Array.from(el.querySelectorAll<HTMLElement>('button'));
169
+ const currentIdx = buttons.indexOf(
170
+ document.activeElement as HTMLElement,
171
+ );
172
+ if (currentIdx === -1) return; // focus is not on one of our buttons — pass through
173
+
174
+ e.preventDefault();
175
+ e.stopPropagation();
176
+
177
+ if (!e.shiftKey) {
178
+ if (currentIdx >= buttons.length - 1) {
179
+ focusCell();
180
+ } else {
181
+ buttons[currentIdx + 1]?.focus();
182
+ }
183
+ } else {
184
+ if (currentIdx <= 0) {
185
+ focusCell();
186
+ } else {
187
+ buttons[currentIdx - 1]?.focus();
188
+ }
189
+ }
190
+ }
191
+ };
192
+
193
+ el.addEventListener('keydown', handleKeyDown);
194
+ return () => el.removeEventListener('keydown', handleKeyDown);
195
+ }, [focusCell, onCancel]);
196
+
197
+ if (loading) {
198
+ return (
199
+ <Box
200
+ dataHook={dataHook}
201
+ verticalAlign="middle"
202
+ width="100%"
203
+ height="100%"
204
+ padding="0 8px"
205
+ >
206
+ <Loader size="tiny" />
207
+ </Box>
208
+ );
209
+ }
210
+
211
+ return (
212
+ <div
213
+ ref={containerRef}
214
+ data-hook={dataHook}
215
+ style={{ width: '100%', height: '100%' }}
216
+ onFocus={handleContainerFocus}
217
+ onBlur={handleContainerBlur}
218
+ >
219
+ <Box
220
+ align="space-between"
221
+ verticalAlign="middle"
222
+ height="100%"
223
+ padding="0 8px"
224
+ style={{ boxSizing: 'border-box' }}
225
+ >
226
+ {value ? (
227
+ /* Has value: clickable thumbnail button with replace overlay */
228
+ <Tooltip content={t('CMS.image.replace')} appendTo="window">
229
+ <Box>
230
+ <button
231
+ type="button"
232
+ data-hook="image-edit-thumbnail"
233
+ onClick={handleCallMedia}
234
+ style={{
235
+ cursor: 'pointer',
236
+ lineHeight: 0,
237
+ position: 'relative',
238
+ background: 'none',
239
+ border: 'none',
240
+ padding: 0,
241
+ }}
242
+ >
243
+ <ImageThumbnail value={value} />
244
+ <div
245
+ style={{
246
+ position: 'absolute',
247
+ inset: 0,
248
+ display: 'flex',
249
+ alignItems: 'center',
250
+ justifyContent: 'center',
251
+ background: 'rgba(0, 0, 0, 0.4)',
252
+ borderRadius: '3px',
253
+ }}
254
+ >
255
+ <Replace
256
+ style={{ color: 'white', width: '18px', height: '18px' }}
257
+ />
258
+ </div>
259
+ </button>
260
+ </Box>
261
+ </Tooltip>
262
+ ) : (
263
+ /* Empty: icon button opens media manager */
264
+ <Tooltip content={t('CMS.image.add')} appendTo="window">
265
+ <IconButton
266
+ size="tiny"
267
+ priority="secondary"
268
+ dataHook="image-edit-add-button"
269
+ onClick={handleCallMedia}
270
+ >
271
+ <Add />
272
+ </IconButton>
273
+ </Tooltip>
274
+ )}
275
+
276
+ {value ? (
277
+ /* Has value: "..." menu with Replace / Replace with URL / Delete */
278
+ <PopoverMenu
279
+ textSize="small"
280
+ appendTo="window"
281
+ triggerElement={
282
+ <Tooltip
283
+ content={t('auto-patterns.fields.more_actions_tooltip')}
284
+ appendTo="window"
285
+ >
286
+ <IconButton
287
+ size="tiny"
288
+ priority="tertiary"
289
+ dataHook="image-edit-more-button"
290
+ >
291
+ <MoreSmall />
292
+ </IconButton>
293
+ </Tooltip>
294
+ }
295
+ >
296
+ <PopoverMenu.MenuItem
297
+ dataHook="image-edit-action-replace"
298
+ text={t('CMS.image.replace')}
299
+ prefixIcon={<Replace />}
300
+ onClick={handleCallMedia}
301
+ />
302
+ <PopoverMenu.MenuItem
303
+ dataHook="image-edit-action-replace-url"
304
+ text={t('CMS.image.replaceWithUrl.button')}
305
+ prefixIcon={<Link />}
306
+ onClick={handleOpenPasteUrl({
307
+ title: pasteUrlTitle,
308
+ value: value ?? undefined,
309
+ })}
310
+ />
311
+ <PopoverMenu.Divider />
312
+ <PopoverMenu.MenuItem
313
+ dataHook="image-edit-action-delete"
314
+ text={t('CMS.image.clear')}
315
+ skin="destructive"
316
+ prefixIcon={<Delete />}
317
+ onClick={handleDelete}
318
+ />
319
+ </PopoverMenu>
320
+ ) : (
321
+ /* Empty: icon button opens paste-URL form */
322
+ <Tooltip
323
+ content={t('CMS.image.replaceWithUrl.button')}
324
+ appendTo="window"
325
+ >
326
+ <IconButton
327
+ size="tiny"
328
+ priority="tertiary"
329
+ dataHook="image-edit-paste-url-button"
330
+ onClick={handleOpenPasteUrl({
331
+ title: pasteUrlTitle,
332
+ value: value ?? undefined,
333
+ })}
334
+ >
335
+ <LinkSmall />
336
+ </IconButton>
337
+ </Tooltip>
338
+ )}
339
+ </Box>
340
+
341
+ <PasteURLForm
342
+ title={title}
343
+ value={value ?? undefined}
344
+ show={isOpen}
345
+ closeModal={handleFormClose}
346
+ onSave={onSave}
347
+ fieldType={MediaFieldType.image}
348
+ />
349
+ </div>
350
+ );
351
+ };
352
+
353
+ CellImageEdit.displayName = 'CellImageEdit';
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import {
3
+ isMediaVideoURI,
4
+ mediaURItoImageURL,
5
+ ThumbnailSize,
6
+ } from '../../auto-field-types';
7
+ import { MediaImage } from '../media-image';
8
+ import {
9
+ CustomUrlOverlay,
10
+ VideoIndicatorOverlay,
11
+ } from '../media-image/overlays';
12
+
13
+ export interface ImageThumbnailProps {
14
+ value?: string | null;
15
+ onLoad?: () => void;
16
+ onError?: () => void;
17
+ onMouseEnter?: () => void;
18
+ onMouseLeave?: () => void;
19
+ dataHook?: string;
20
+ }
21
+
22
+ export const ImageThumbnail: React.FC<ImageThumbnailProps> = ({
23
+ value,
24
+ onLoad,
25
+ onError,
26
+ onMouseEnter,
27
+ onMouseLeave,
28
+ dataHook,
29
+ }) => {
30
+ const src = value ? mediaURItoImageURL(value, ThumbnailSize.S) : undefined;
31
+
32
+ return (
33
+ <div
34
+ data-hook={dataHook}
35
+ data-empty={!value || undefined}
36
+ style={{ width: '36px', height: '36px', borderRadius: '3px', flexShrink: 0, position: 'relative' }}
37
+ >
38
+ <MediaImage
39
+ src={src}
40
+ alt=""
41
+ suspended
42
+ borderRadius="3px"
43
+ transparent
44
+ size={ThumbnailSize.S}
45
+ width="36px"
46
+ height="36px"
47
+ onLoad={onLoad}
48
+ onError={onError}
49
+ onMouseEnter={onMouseEnter}
50
+ onMouseLeave={onMouseLeave}
51
+ overlay={
52
+ value && isMediaVideoURI(value) ? (
53
+ <VideoIndicatorOverlay src={value} size="small" />
54
+ ) : (
55
+ <CustomUrlOverlay src={value} size="small" />
56
+ )
57
+ }
58
+ />
59
+ </div>
60
+ );
61
+ };
62
+
63
+ ImageThumbnail.displayName = 'ImageThumbnail';
@@ -0,0 +1,69 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { Box, Popover } from '@wix/design-system';
3
+ import { mediaURItoImageURL, ThumbnailSize } from '../../auto-field-types';
4
+ import { MediaImage } from '../media-image';
5
+ import { ImageThumbnail } from './image-thumbnail';
6
+
7
+ export interface ImageViewProps {
8
+ value: string | null | undefined;
9
+ dataHook?: string;
10
+ }
11
+
12
+ export const ImageView: React.FC<ImageViewProps> = ({ value, dataHook }) => {
13
+ const [isPreviewShown, setIsPreviewShown] = useState(false);
14
+
15
+ const showPreview = useCallback(() => setIsPreviewShown(true), []);
16
+ const hidePreview = useCallback(() => setIsPreviewShown(false), []);
17
+
18
+ return (
19
+ <Box
20
+ dataHook={dataHook}
21
+ verticalAlign="middle"
22
+ padding="0 8px"
23
+ height="100%"
24
+ width="100%"
25
+ >
26
+ <Box minWidth="0" style={{ flex: 1 }}>
27
+ {value ? (
28
+ <Popover
29
+ showArrow={false}
30
+ shown={isPreviewShown}
31
+ appendTo="viewport"
32
+ placement="bottom-start"
33
+ showDelay={300}
34
+ moveBy={{ y: 4, x: 0 }}
35
+ zIndex={9999}
36
+ fluid
37
+ >
38
+ <Popover.Element>
39
+ <ImageThumbnail
40
+ value={value}
41
+ dataHook="image-thumbnail"
42
+ onMouseEnter={showPreview}
43
+ onMouseLeave={hidePreview}
44
+ />
45
+ </Popover.Element>
46
+ <Popover.Content>
47
+ <Box
48
+ width="204px"
49
+ style={{ borderRadius: '3px', overflow: 'hidden', lineHeight: 0 }}
50
+ >
51
+ <MediaImage
52
+ src={mediaURItoImageURL(value, ThumbnailSize.L)}
53
+ alt=""
54
+ fit="contain"
55
+ transparent
56
+ size={ThumbnailSize.L}
57
+ />
58
+ </Box>
59
+ </Popover.Content>
60
+ </Popover>
61
+ ) : (
62
+ <ImageThumbnail value={value} dataHook="image-thumbnail" />
63
+ )}
64
+ </Box>
65
+ </Box>
66
+ );
67
+ };
68
+
69
+ ImageView.displayName = 'ImageView';
@@ -0,0 +1,3 @@
1
+ export * from './image-thumbnail';
2
+ export * from './image-view';
3
+ export * from './cell-image-edit';
@@ -18,3 +18,4 @@ export * from './email';
18
18
  export * from './boolean';
19
19
  export * from './number-input';
20
20
  export * from './url';
21
+ export * from './image';
@@ -5,7 +5,7 @@ import { classesRuntime, statesRuntime } from "../../stylable-esm-runtime.js";
5
5
 
6
6
 
7
7
 
8
- var _namespace_ = "pasteurlbutton2188405185";
8
+ var _namespace_ = "pasteurlbutton3952587235";
9
9
  var _style_ = classesRuntime.bind(null, _namespace_);
10
10
 
11
11
  export var cssStates = statesRuntime.bind(null, _namespace_);
@@ -13,7 +13,7 @@ export var style: import("@stylable/runtime").STFunction = _style_;
13
13
  export var st: import("@stylable/runtime").STFunction = _style_;
14
14
 
15
15
  export var namespace = _namespace_;
16
- export var classes = {"root":"pasteurlbutton2188405185__root","iconButtonTooltip":"pasteurlbutton2188405185__iconButtonTooltip","iconButton":"pasteurlbutton2188405185__iconButton"};
16
+ export var classes = {"root":"pasteurlbutton3952587235__root","iconButtonTooltip":"pasteurlbutton3952587235__iconButtonTooltip","iconButton":"pasteurlbutton3952587235__iconButton"};
17
17
  export var keyframes = {};
18
18
  export var layers = {};
19
19
  export var containers = {};
@@ -5,7 +5,7 @@ import { classesRuntime, statesRuntime } from "../../stylable-esm-runtime.js";
5
5
 
6
6
 
7
7
 
8
- var _namespace_ = "formmediagalleryfield3030618679";
8
+ var _namespace_ = "formmediagalleryfield3106985161";
9
9
  var _style_ = classesRuntime.bind(null, _namespace_);
10
10
 
11
11
  export var cssStates = statesRuntime.bind(null, _namespace_);
@@ -13,7 +13,7 @@ export var style: import("@stylable/runtime").STFunction = _style_;
13
13
  export var st: import("@stylable/runtime").STFunction = _style_;
14
14
 
15
15
  export var namespace = _namespace_;
16
- export var classes = {"root":"formmediagalleryfield3030618679__root","container":"formmediagalleryfield3030618679__container","viewBox":"formmediagalleryfield3030618679__viewBox","addItemWrapper":"formmediagalleryfield3030618679__addItemWrapper","addItem":"formmediagalleryfield3030618679__addItem","overlay":"formmediagalleryfield3030618679__overlay","image":"formmediagalleryfield3030618679__image","previewImage":"formmediagalleryfield3030618679__previewImage","previewVideo":"formmediagalleryfield3030618679__previewVideo"};
16
+ export var classes = {"root":"formmediagalleryfield3106985161__root","container":"formmediagalleryfield3106985161__container","viewBox":"formmediagalleryfield3106985161__viewBox","addItemWrapper":"formmediagalleryfield3106985161__addItemWrapper","addItem":"formmediagalleryfield3106985161__addItem","overlay":"formmediagalleryfield3106985161__overlay","image":"formmediagalleryfield3106985161__image","previewImage":"formmediagalleryfield3106985161__previewImage","previewVideo":"formmediagalleryfield3106985161__previewVideo"};
17
17
  export var keyframes = {};
18
18
  export var layers = {};
19
19
  export var containers = {};
@@ -5,7 +5,7 @@ import { classesRuntime, statesRuntime } from "../../stylable-esm-runtime.js";
5
5
 
6
6
 
7
7
 
8
- var _namespace_ = "mediaimage2658102972";
8
+ var _namespace_ = "mediaimage1708830916";
9
9
  var _style_ = classesRuntime.bind(null, _namespace_);
10
10
 
11
11
  export var cssStates = statesRuntime.bind(null, _namespace_);
@@ -13,12 +13,12 @@ export var style: import("@stylable/runtime").STFunction = _style_;
13
13
  export var st: import("@stylable/runtime").STFunction = _style_;
14
14
 
15
15
  export var namespace = _namespace_;
16
- export var classes = {"root":"mediaimage2658102972__root"};
16
+ export var classes = {"root":"mediaimage1708830916__root"};
17
17
  export var keyframes = {};
18
18
  export var layers = {};
19
19
  export var containers = {};
20
20
  export var stVars = {};
21
- export var vars = {"wds-color-red-200":"--wds-color-red-200","wsr-color-R20":"--wsr-color-R20","wds-color-black-550":"--wds-color-black-550","wsr-color-D55":"--wsr-color-D55","from":"--mediaimage2658102972-from","to":"--mediaimage2658102972-to"};
21
+ export var vars = {"wds-color-red-200":"--wds-color-red-200","wsr-color-R20":"--wsr-color-R20","wds-color-black-550":"--wds-color-black-550","wsr-color-D55":"--wsr-color-D55","from":"--mediaimage1708830916-from","to":"--mediaimage1708830916-to"};
22
22
 
23
23
 
24
24
 
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { Link as LinkIcon } from '@wix/wix-ui-icons-common';
3
+ import { st, classes } from './overlay.st.css.js';
4
+ import type { IconOverlayProps } from './types';
5
+
6
+ /** Shows a link icon when the src is a custom (non-wix://) URL. */
7
+ export const CustomUrlOverlay: React.FC<IconOverlayProps> = ({
8
+ src,
9
+ size = 'small',
10
+ placement = 'bottomLeft',
11
+ }) => {
12
+ const isWixUri = src?.startsWith('wix:');
13
+ return src && !isWixUri ? (
14
+ <LinkIcon className={st(classes.iconOverlay, { size, placement })} />
15
+ ) : null;
16
+ };
17
+
18
+ CustomUrlOverlay.displayName = 'CustomUrlOverlay';
@@ -1,2 +1,3 @@
1
1
  export { VideoIndicatorOverlay } from './video-indicator-overlay';
2
+ export { CustomUrlOverlay } from './custom-url-overlay';
2
3
  export type { IconOverlayProps, IconOverlaySize, IconOverlayPlacement, MediaImageOverlayProps } from './types';
@@ -5,7 +5,7 @@ import { classesRuntime, statesRuntime } from "../../../stylable-esm-runtime.js"
5
5
 
6
6
 
7
7
 
8
- var _namespace_ = "overlay3631758939";
8
+ var _namespace_ = "overlay636547370";
9
9
  var _style_ = classesRuntime.bind(null, _namespace_);
10
10
 
11
11
  export var cssStates = statesRuntime.bind(null, _namespace_);
@@ -13,12 +13,12 @@ export var style: import("@stylable/runtime").STFunction = _style_;
13
13
  export var st: import("@stylable/runtime").STFunction = _style_;
14
14
 
15
15
  export var namespace = _namespace_;
16
- export var classes = {"root":"overlay3631758939__root","iconOverlay":"overlay3631758939__iconOverlay"};
16
+ export var classes = {"root":"overlay636547370__root","iconOverlay":"overlay636547370__iconOverlay"};
17
17
  export var keyframes = {};
18
18
  export var layers = {};
19
19
  export var containers = {};
20
20
  export var stVars = {"iconShadow":"drop-shadow(0 1px 1px rgba(22, 45, 61, 0.6))"};
21
- export var vars = {"wds-color-white":"--wds-color-white","wsr-color-D80":"--overlay3631758939-wsr-color-D80"};
21
+ export var vars = {"wds-color-white":"--wds-color-white","wsr-color-D80":"--overlay636547370-wsr-color-D80"};
22
22
 
23
23
 
24
24
 
@@ -2,7 +2,7 @@ export type IconOverlaySize = 'small' | 'medium' | 'large';
2
2
  export type IconOverlayPlacement = 'topLeft' | 'bottomLeft';
3
3
 
4
4
  export interface MediaImageOverlayProps {
5
- src?: string;
5
+ src?: string | null;
6
6
  }
7
7
 
8
8
  export interface IconOverlayProps extends MediaImageOverlayProps {
@@ -5,7 +5,7 @@ import { classesRuntime, statesRuntime } from "../../stylable-esm-runtime.js";
5
5
 
6
6
 
7
7
 
8
- var _namespace_ = "medialoader3771116817";
8
+ var _namespace_ = "medialoader2708273655";
9
9
  var _style_ = classesRuntime.bind(null, _namespace_);
10
10
 
11
11
  export var cssStates = statesRuntime.bind(null, _namespace_);
@@ -13,7 +13,7 @@ export var style: import("@stylable/runtime").STFunction = _style_;
13
13
  export var st: import("@stylable/runtime").STFunction = _style_;
14
14
 
15
15
  export var namespace = _namespace_;
16
- export var classes = {"root":"medialoader3771116817__root","loader":"medialoader3771116817__loader"};
16
+ export var classes = {"root":"medialoader2708273655__root","loader":"medialoader2708273655__loader"};
17
17
  export var keyframes = {};
18
18
  export var layers = {};
19
19
  export var containers = {};
@@ -5,7 +5,7 @@ import { classesRuntime, statesRuntime } from "../../../stylable-esm-runtime.js"
5
5
 
6
6
 
7
7
 
8
- var _namespace_ = "mediatag1783400040";
8
+ var _namespace_ = "mediatag2186192433";
9
9
  var _style_ = classesRuntime.bind(null, _namespace_);
10
10
 
11
11
  export var cssStates = statesRuntime.bind(null, _namespace_);
@@ -13,7 +13,7 @@ export var style: import("@stylable/runtime").STFunction = _style_;
13
13
  export var st: import("@stylable/runtime").STFunction = _style_;
14
14
 
15
15
  export var namespace = _namespace_;
16
- export var classes = {"root":"mediatag1783400040__root"};
16
+ export var classes = {"root":"mediatag2186192433__root"};
17
17
  export var keyframes = {};
18
18
  export var layers = {};
19
19
  export var containers = {};
@@ -5,7 +5,7 @@ import { classesRuntime, statesRuntime } from "../../../stylable-esm-runtime.js"
5
5
 
6
6
 
7
7
 
8
- var _namespace_ = "formmultidocument3562652977";
8
+ var _namespace_ = "formmultidocument3882726463";
9
9
  var _style_ = classesRuntime.bind(null, _namespace_);
10
10
 
11
11
  export var cssStates = statesRuntime.bind(null, _namespace_);
@@ -13,7 +13,7 @@ export var style: import("@stylable/runtime").STFunction = _style_;
13
13
  export var st: import("@stylable/runtime").STFunction = _style_;
14
14
 
15
15
  export var namespace = _namespace_;
16
- export var classes = {"root":"formmultidocument3562652977__root","viewRoot":"formmultidocument3562652977__viewRoot","singleViewWrapper":"formmultidocument3562652977__singleViewWrapper","addDocumentButton":"formmultidocument3562652977__addDocumentButton"};
16
+ export var classes = {"root":"formmultidocument3882726463__root","viewRoot":"formmultidocument3882726463__viewRoot","singleViewWrapper":"formmultidocument3882726463__singleViewWrapper","addDocumentButton":"formmultidocument3882726463__addDocumentButton"};
17
17
  export var keyframes = {};
18
18
  export var layers = {};
19
19
  export var containers = {};