tinacms 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -54,6 +54,15 @@ function useTina(props) {
54
54
  }
55
55
  }
56
56
  if (fieldName) {
57
+ if (lastHoveredField !== null) {
58
+ lastHoveredField = null;
59
+ if (isInTinaIframe) {
60
+ parent.postMessage(
61
+ { type: "field:hovered", fieldName: null },
62
+ window.location.origin
63
+ );
64
+ }
65
+ }
57
66
  if (isInTinaIframe) {
58
67
  parent.postMessage(
59
68
  { type: "field:selected", fieldName },
@@ -61,6 +70,40 @@ function useTina(props) {
61
70
  );
62
71
  }
63
72
  }
73
+ }, mouseEnterHandler = function(e) {
74
+ if (!(e.target instanceof Element)) {
75
+ return;
76
+ }
77
+ const attributeNames = e.target.getAttributeNames();
78
+ const tinaAttribute = attributeNames.find(
79
+ (name) => name.startsWith("data-tina-field")
80
+ );
81
+ let fieldName;
82
+ if (tinaAttribute) {
83
+ fieldName = e.target.getAttribute(tinaAttribute);
84
+ } else {
85
+ const ancestor = e.target.closest(
86
+ "[data-tina-field], [data-tina-field-overlay]"
87
+ );
88
+ if (ancestor) {
89
+ const attributeNames2 = ancestor.getAttributeNames();
90
+ const tinaAttribute2 = attributeNames2.find(
91
+ (name) => name.startsWith("data-tina-field")
92
+ );
93
+ if (tinaAttribute2) {
94
+ fieldName = ancestor.getAttribute(tinaAttribute2);
95
+ }
96
+ }
97
+ }
98
+ if (fieldName && fieldName !== lastHoveredField) {
99
+ lastHoveredField = fieldName;
100
+ if (isInTinaIframe) {
101
+ parent.postMessage(
102
+ { type: "field:hovered", fieldName },
103
+ window.location.origin
104
+ );
105
+ }
106
+ }
64
107
  };
65
108
  const style = document.createElement("style");
66
109
  style.type = "text/css";
@@ -74,6 +117,15 @@ function useTina(props) {
74
117
  outline: 2px solid rgba(34,150,254,1);
75
118
  cursor: pointer;
76
119
  }
120
+ [data-tina-field-focused] {
121
+ outline: 2px dashed #C2410C !important;
122
+ box-shadow: none !important;
123
+ }
124
+ [data-tina-field-focused]:hover {
125
+ box-shadow: inset 100vi 100vh rgba(194, 65, 12, 0.3) !important;
126
+ outline: 2px solid #C2410C !important;
127
+ cursor: pointer;
128
+ }
77
129
  [data-tina-field-overlay] {
78
130
  outline: 2px dashed rgba(34,150,254,0.5);
79
131
  position: relative;
@@ -94,12 +146,19 @@ function useTina(props) {
94
146
  [data-tina-field-overlay]:hover::after {
95
147
  opacity: 1;
96
148
  }
149
+ [data-tina-field-overlay][data-tina-field-focused]::after {
150
+ background-color: rgba(194, 65, 12, 0.3);
151
+ opacity: 1;
152
+ }
97
153
  `;
98
154
  document.head.appendChild(style);
99
155
  document.body.classList.add("__tina-quick-editing-enabled");
156
+ let lastHoveredField = null;
100
157
  document.addEventListener("click", mouseDownHandler, true);
158
+ document.addEventListener("mouseenter", mouseEnterHandler, true);
101
159
  return () => {
102
160
  document.removeEventListener("click", mouseDownHandler, true);
161
+ document.removeEventListener("mouseenter", mouseEnterHandler, true);
103
162
  document.body.classList.remove("__tina-quick-editing-enabled");
104
163
  style.remove();
105
164
  };
@@ -113,6 +172,7 @@ function useTina(props) {
113
172
  });
114
173
  }
115
174
  }, [id]);
175
+ const lastFocusedFieldRef = React.useRef(null);
116
176
  React.useEffect(() => {
117
177
  const { experimental___selectFormByFormId, ...rest } = props;
118
178
  parent.postMessage({ type: "open", ...rest, id }, window.location.origin);
@@ -142,6 +202,40 @@ function useTina(props) {
142
202
  );
143
203
  }
144
204
  }
205
+ if (event.data.type === "field:set-focused") {
206
+ const newFieldName = event.data.fieldName;
207
+ if (newFieldName === lastFocusedFieldRef.current) {
208
+ return;
209
+ }
210
+ lastFocusedFieldRef.current = newFieldName;
211
+ const allTinaFields = document.querySelectorAll("[data-tina-field]");
212
+ allTinaFields.forEach((el) => {
213
+ el.removeAttribute("data-tina-field-focused");
214
+ });
215
+ if (newFieldName) {
216
+ let targetElement = document.querySelector(
217
+ `[data-tina-field="${newFieldName}"]`
218
+ );
219
+ if (!targetElement) {
220
+ const allFields = Array.from(allTinaFields);
221
+ targetElement = allFields.find((el) => {
222
+ const fieldValue = el.getAttribute("data-tina-field");
223
+ return fieldValue && fieldValue.endsWith(newFieldName.split("---")[1]);
224
+ });
225
+ }
226
+ if (targetElement) {
227
+ targetElement.setAttribute("data-tina-field-focused", "true");
228
+ const rect = targetElement.getBoundingClientRect();
229
+ const isInViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
230
+ if (!isInViewport) {
231
+ targetElement.scrollIntoView({
232
+ behavior: "smooth",
233
+ block: "center"
234
+ });
235
+ }
236
+ }
237
+ }
238
+ }
145
239
  };
146
240
  window.addEventListener("message", handleMessage);
147
241
  return () => {
@@ -1,2 +1,3 @@
1
1
  import * as React from 'react';
2
2
  export declare const ActiveFieldIndicator: () => React.JSX.Element;
3
+ export declare const HoveredFieldIndicator: () => React.JSX.Element;
@@ -4,6 +4,6 @@ export interface PasswordFieldProps extends a {
4
4
  error?: boolean;
5
5
  ref?: any;
6
6
  }
7
- export declare const passwordFieldClasses = "shadow-inner focus:shadow-outline focus:border-blue-500 focus:outline-none block text-base placeholder:text-gray-300 px-3 py-2 text-gray-600 w-full bg-white border border-gray-200 transition-all ease-out duration-150 focus:text-gray-900 rounded";
7
+ export declare const passwordFieldClasses = "shadow-inner focus:shadow-outline focus:border-tina-orange-dark focus:outline-none block text-base placeholder:text-gray-300 px-3 py-2 text-gray-600 w-full bg-white border border-gray-200 transition-all ease-out duration-150 focus:text-gray-900 rounded";
8
8
  export declare const BasePasswordField: React.ForwardRefExoticComponent<Omit<PasswordFieldProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
9
9
  export {};
@@ -18,6 +18,6 @@ export interface SelectProps {
18
18
  options?: (Option | string)[];
19
19
  className?: string;
20
20
  }
21
- export declare const selectFieldClasses = "shadow appearance-none h-full bg-white block pl-3 pr-8 py-2 truncate w-full text-base cursor-pointer border border-gray-200 focus:outline-none focus:shadow-outline focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded";
21
+ export declare const selectFieldClasses = "shadow appearance-none h-full bg-white block pl-3 pr-8 py-2 truncate w-full text-base cursor-pointer border border-gray-200 focus:outline-none focus:shadow-outline focus:ring-tina-orange-dark focus:border-tina-orange-dark sm:text-sm rounded";
22
22
  export declare const Select: React.FC<SelectProps>;
23
23
  export {};
@@ -4,6 +4,6 @@ export interface TextFieldProps extends a {
4
4
  error?: boolean;
5
5
  ref?: any;
6
6
  }
7
- export declare const textFieldClasses = "shadow-inner focus:shadow-outline focus:border-blue-500 focus:outline-none block text-base placeholder:text-gray-300 px-3 py-2 text-gray-600 w-full bg-white border border-gray-200 transition-all ease-out duration-150 focus:text-gray-900 rounded";
7
+ export declare const textFieldClasses = "shadow-inner focus:shadow-outline focus:border-tina-orange-dark focus-visible:ring-0 focus:outline-none focus:ring-2 block text-base placeholder:text-gray-300 px-3 py-2 text-gray-600 w-full bg-white border border-gray-200 transition-all ease-out duration-150 focus:text-gray-900 rounded";
8
8
  export declare const BaseTextField: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
9
9
  export {};
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { FieldProps } from './field-props';
3
- import { Form } from '../../forms';
3
+ import { Form, Field } from '../../forms';
4
4
  export type InputFieldType<ExtraFieldProps, InputProps> = FieldProps<InputProps> & ExtraFieldProps;
5
5
  export declare function wrapFieldsWithMeta<ExtraFieldProps = {}, InputProps = {}>(Field: React.FunctionComponent<InputFieldType<ExtraFieldProps, InputProps>> | React.ComponentClass<InputFieldType<ExtraFieldProps, InputProps>>): (props: InputFieldType<ExtraFieldProps, InputProps>) => React.JSX.Element;
6
6
  /**
@@ -21,11 +21,17 @@ interface FieldMetaProps extends React.HTMLAttributes<HTMLElement> {
21
21
  margin?: boolean;
22
22
  index?: number;
23
23
  tinaForm: Form;
24
+ field?: Field;
25
+ focusIntent?: boolean;
26
+ hoverIntent?: boolean;
24
27
  }
25
- export declare const FieldMeta: ({ name, label, description, error, margin, children, index, tinaForm, ...props }: FieldMetaProps) => React.JSX.Element;
26
- export declare const FieldWrapper: ({ margin, children, ...props }: {
28
+ export declare const FieldMeta: ({ name, label, description, error, margin, children, index, tinaForm, field, focusIntent, hoverIntent, ...props }: FieldMetaProps) => React.JSX.Element;
29
+ export declare const FieldWrapper: ({ margin, children, field, "data-tina-field-active": dataActive, "data-tina-field-hovering": dataHovering, ...props }: {
27
30
  margin: boolean;
28
31
  children: React.ReactNode;
32
+ field?: Field;
33
+ "data-tina-field-active"?: string;
34
+ "data-tina-field-hovering"?: string;
29
35
  } & Partial<React.ComponentPropsWithoutRef<"div">>) => React.JSX.Element;
30
36
  export interface FieldLabel extends React.HTMLAttributes<HTMLLabelElement> {
31
37
  children?: any | any[];
@@ -3,11 +3,11 @@ import * as React from 'react';
3
3
  export interface FieldsBuilderProps {
4
4
  form: Form;
5
5
  activeFieldName?: string;
6
+ hoveringFieldName?: string;
6
7
  fields: Field[];
7
8
  padding?: boolean;
8
9
  }
9
- export declare function FieldsBuilder({ form, fields, activeFieldName, padding, }: FieldsBuilderProps): React.JSX.Element;
10
- export declare const FieldsGroup: ({ padding, children, }: {
11
- padding?: boolean;
10
+ export declare function FieldsBuilder({ form, fields, activeFieldName, hoveringFieldName, padding, }: FieldsBuilderProps): React.JSX.Element;
11
+ export declare const FieldsGroup: ({ children, }: {
12
12
  children?: any | any[];
13
13
  }) => React.JSX.Element;
@@ -5,6 +5,7 @@ export interface FormBuilderProps {
5
5
  form: {
6
6
  tinaForm: Form;
7
7
  activeFieldName?: string;
8
+ hoveringFieldName?: string;
8
9
  };
9
10
  hideFooter?: boolean;
10
11
  label?: string;
@@ -15,14 +15,16 @@ export interface Field<F extends Field = AnyField> {
15
15
  defaultValue?: any;
16
16
  namespace?: string[];
17
17
  fields?: F[];
18
+ width?: 'full' | 'half';
18
19
  /**
19
20
  * Focus events can come from outside of the component, this is not
20
21
  * a guarantee that the given field will receive focus since that functionality
21
22
  * needs to be built on a per-component basis.
22
23
  *
23
- * This is also a one-way stree. The "active field" for a given form isn't
24
+ * This is also a one-way street. The "active field" for a given form isn't
24
25
  * necessarily updated when a user clicks on a new field. So you can have a
25
26
  * field which is marked as the active field, and have focus on another field
26
27
  */
27
- experimental_focusIntent?: boolean;
28
+ focusIntent?: boolean;
29
+ hoverIntent?: boolean;
28
30
  }
@@ -1,5 +1,24 @@
1
1
  import { Form } from './react-tinacms';
2
2
  import { TinaCMS } from './tina-cms';
3
+ export declare const ACTION_TYPES: {
4
+ readonly FORMS_ADD: "forms:add";
5
+ readonly FORMS_REMOVE: "forms:remove";
6
+ readonly FORMS_CLEAR: "forms:clear";
7
+ readonly FORMS_SET_ACTIVE_FORM_ID: "forms:set-active-form-id";
8
+ readonly FORMS_SET_ACTIVE_FIELD_NAME: "forms:set-active-field-name";
9
+ readonly FORMS_SET_HOVERED_FIELD_NAME: "forms:set-hovered-field-name";
10
+ readonly FORM_LISTS_ADD: "form-lists:add";
11
+ readonly FORM_LISTS_REMOVE: "form-lists:remove";
12
+ readonly FORM_LISTS_CLEAR: "form-lists:clear";
13
+ readonly SET_EDIT_MODE: "set-edit-mode";
14
+ readonly INCREMENT_OPERATION_INDEX: "increment-operation-index";
15
+ readonly SET_QUICK_EDITING_SUPPORTED: "set-quick-editing-supported";
16
+ readonly SET_QUICK_EDITING_ENABLED: "set-quick-editing-enabled";
17
+ readonly TOGGLE_QUICK_EDITING_ENABLED: "toggle-quick-editing-enabled";
18
+ readonly TOGGLE_EDIT_STATE: "toggle-edit-state";
19
+ readonly SIDEBAR_SET_DISPLAY_STATE: "sidebar:set-display-state";
20
+ readonly SIDEBAR_SET_LOADING_STATE: "sidebar:set-loading-state";
21
+ };
3
22
  type FormListItem = {
4
23
  type: 'document';
5
24
  path: string;
@@ -22,50 +41,56 @@ type Breadcrumb = {
22
41
  namespace: string[];
23
42
  };
24
43
  export type TinaAction = {
25
- type: 'forms:add';
44
+ type: typeof ACTION_TYPES.FORMS_ADD;
26
45
  value: Form;
27
46
  } | {
28
- type: 'forms:remove';
47
+ type: typeof ACTION_TYPES.FORMS_REMOVE;
29
48
  value: string;
30
49
  } | {
31
- type: 'forms:clear';
50
+ type: typeof ACTION_TYPES.FORMS_CLEAR;
32
51
  } | {
33
- type: 'form-lists:add';
52
+ type: typeof ACTION_TYPES.FORM_LISTS_ADD;
34
53
  value: FormList;
35
54
  } | {
36
- type: 'form-lists:remove';
55
+ type: typeof ACTION_TYPES.FORM_LISTS_REMOVE;
37
56
  value: string;
38
57
  } | {
39
- type: 'forms:set-active-form-id';
58
+ type: typeof ACTION_TYPES.FORMS_SET_ACTIVE_FORM_ID;
40
59
  value: string;
41
60
  } | {
42
- type: 'forms:set-active-field-name';
61
+ type: typeof ACTION_TYPES.FORMS_SET_ACTIVE_FIELD_NAME;
43
62
  value: {
44
63
  formId: string;
45
64
  fieldName: string;
46
65
  };
47
66
  } | {
48
- type: 'form-lists:clear';
67
+ type: typeof ACTION_TYPES.FORMS_SET_HOVERED_FIELD_NAME;
68
+ value: {
69
+ formId: string;
70
+ fieldName: string | null;
71
+ };
72
+ } | {
73
+ type: typeof ACTION_TYPES.FORM_LISTS_CLEAR;
49
74
  } | {
50
- type: 'set-edit-mode';
75
+ type: typeof ACTION_TYPES.SET_EDIT_MODE;
51
76
  value: 'visual' | 'basic';
52
77
  } | {
53
- type: 'increment-operation-index';
78
+ type: typeof ACTION_TYPES.INCREMENT_OPERATION_INDEX;
54
79
  } | {
55
- type: 'set-quick-editing-supported';
80
+ type: typeof ACTION_TYPES.SET_QUICK_EDITING_SUPPORTED;
56
81
  value: boolean;
57
82
  } | {
58
- type: 'set-quick-editing-enabled';
83
+ type: typeof ACTION_TYPES.SET_QUICK_EDITING_ENABLED;
59
84
  value?: boolean;
60
85
  } | {
61
- type: 'toggle-quick-editing-enabled';
86
+ type: typeof ACTION_TYPES.TOGGLE_QUICK_EDITING_ENABLED;
62
87
  } | {
63
- type: 'toggle-edit-state';
88
+ type: typeof ACTION_TYPES.TOGGLE_EDIT_STATE;
64
89
  } | {
65
- type: 'sidebar:set-display-state';
90
+ type: typeof ACTION_TYPES.SIDEBAR_SET_DISPLAY_STATE;
66
91
  value: TinaState['sidebarDisplayState'] | 'openOrFull';
67
92
  } | {
68
- type: 'sidebar:set-loading-state';
93
+ type: typeof ACTION_TYPES.SIDEBAR_SET_LOADING_STATE;
69
94
  value: boolean;
70
95
  };
71
96
  export interface TinaState {
@@ -83,6 +108,7 @@ export interface TinaState {
83
108
  forms: {
84
109
  activeFieldName?: string | null;
85
110
  tinaForm: Form;
111
+ hoveringFieldName?: string | null;
86
112
  }[];
87
113
  formLists: FormList[];
88
114
  editingMode: 'visual' | 'basic';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "tinacms",
3
3
  "type": "module",
4
4
  "typings": "dist/index.d.ts",
5
- "version": "3.1.2",
5
+ "version": "3.2.0",
6
6
  "main": "dist/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "exports": {
@@ -83,7 +83,7 @@
83
83
  "cmdk": "^1.0.4",
84
84
  "color-string": "^1.9.1",
85
85
  "crypto-js": "^4.2.0",
86
- "date-fns": "2.30.0",
86
+ "date-fns": "4.1.0",
87
87
  "es-toolkit": "^1.42.0",
88
88
  "final-form": "4.20.10",
89
89
  "final-form-arrays": "^3.1.0",
@@ -100,7 +100,7 @@
100
100
  "prop-types": "15.7.2",
101
101
  "react-colorful": "^5.6.1",
102
102
  "react-datetime": "^3.3.1",
103
- "react-day-picker": "^9.11.1",
103
+ "react-day-picker": "^9.13.0",
104
104
  "react-dropzone": "14.2.3",
105
105
  "react-final-form": "^6.5.9",
106
106
  "react-icons": "^5.4.0",
@@ -110,9 +110,9 @@
110
110
  "webfontloader": "1.6.28",
111
111
  "yup": "^1.6.1",
112
112
  "zod": "^3.24.2",
113
- "@tinacms/mdx": "2.0.1",
114
- "@tinacms/schema-tools": "2.1.0",
115
- "@tinacms/search": "1.1.7"
113
+ "@tinacms/mdx": "2.0.2",
114
+ "@tinacms/search": "1.1.9",
115
+ "@tinacms/schema-tools": "2.2.0"
116
116
  },
117
117
  "devDependencies": {
118
118
  "@graphql-tools/utils": "^10.8.1",