@visns-studio/visns-components 6.3.11 → 6.3.12

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/package.json CHANGED
@@ -91,7 +91,7 @@
91
91
  "react-dom": "^17.0.0 || ^18.0.0"
92
92
  },
93
93
  "name": "@visns-studio/visns-components",
94
- "version": "6.3.11",
94
+ "version": "6.3.12",
95
95
  "description": "Various packages to assist in the development of our Custom Applications.",
96
96
  "main": "src/index.js",
97
97
  "files": [
@@ -152,6 +152,9 @@ function Field({
152
152
  const editorRef = useRef(null);
153
153
  const fileImageInputRef = useRef(null);
154
154
  const containerRef = useRef(null);
155
+ // Wrapper for the `colour` field's popup picker — used by the
156
+ // click-outside handler below to tell inside clicks from outside ones
157
+ const colorPickerRef = useRef(null);
155
158
  const [autoValue, setAutoValue] = useState('');
156
159
  const [colorPickerShow, setColorPickerShow] = useState(false);
157
160
  const [counter, setCounter] = useState(0);
@@ -163,6 +166,42 @@ function Field({
163
166
  const [emailSuggestions, setEmailSuggestions] = useState([]);
164
167
  const [showEmailSuggestions, setShowEmailSuggestions] = useState(false);
165
168
 
169
+ /**
170
+ * `colour` field popup picker: dismiss the popover when the user clicks
171
+ * anywhere outside it or presses Escape. Picking a colour inside the
172
+ * popover deliberately keeps it open (users drag around the gradient).
173
+ * Declared here — top level of the component — because the field itself
174
+ * is rendered from a switch further down.
175
+ */
176
+ useEffect(() => {
177
+ if (!colorPickerShow) {
178
+ return;
179
+ }
180
+
181
+ const onMouseDown = (event) => {
182
+ if (
183
+ colorPickerRef.current &&
184
+ !colorPickerRef.current.contains(event.target)
185
+ ) {
186
+ setColorPickerShow(false);
187
+ }
188
+ };
189
+
190
+ const onKeyDown = (event) => {
191
+ if (event.key === 'Escape') {
192
+ setColorPickerShow(false);
193
+ }
194
+ };
195
+
196
+ document.addEventListener('mousedown', onMouseDown);
197
+ document.addEventListener('keydown', onKeyDown);
198
+
199
+ return () => {
200
+ document.removeEventListener('mousedown', onMouseDown);
201
+ document.removeEventListener('keydown', onKeyDown);
202
+ };
203
+ }, [colorPickerShow]);
204
+
166
205
  /**
167
206
  * Conflict warnings (opt-in via `settings.conflict`): the field polls
168
207
  * `conflict.url` for a map of {optionId: info} describing options that
@@ -1069,46 +1108,77 @@ function Field({
1069
1108
  ))}
1070
1109
  </div>
1071
1110
  );
1072
- case 'colour':
1111
+ case 'colour': {
1112
+ const hasColour = inputValue && inputValue !== 'null';
1113
+ const toggleColorPicker = () => {
1114
+ if (isFieldReadOnly) {
1115
+ return;
1116
+ }
1117
+
1118
+ setColorPickerShow((show) => !show);
1119
+ };
1120
+
1073
1121
  return (
1074
- <div className="cpicker-inline">
1075
- <div className="cpicker-preview-container">
1122
+ <div
1123
+ className="cpicker-inline cpicker-popup"
1124
+ ref={colorPickerRef}
1125
+ >
1126
+ <div
1127
+ className="cpicker-trigger"
1128
+ role="button"
1129
+ tabIndex={isFieldReadOnly ? -1 : 0}
1130
+ aria-haspopup="dialog"
1131
+ aria-expanded={colorPickerShow}
1132
+ aria-disabled={isFieldReadOnly}
1133
+ onClick={toggleColorPicker}
1134
+ onKeyDown={(event) => {
1135
+ if (event.key === 'Enter' || event.key === ' ') {
1136
+ event.preventDefault();
1137
+ toggleColorPicker();
1138
+ }
1139
+ }}
1140
+ >
1076
1141
  <div
1077
1142
  className="cpicker__box"
1078
- data-color={inputValue && inputValue !== 'null' ? inputValue : 'No colour selected'}
1143
+ data-color={
1144
+ hasColour ? inputValue : 'No colour selected'
1145
+ }
1079
1146
  style={
1080
- inputValue && inputValue !== 'null'
1147
+ hasColour
1081
1148
  ? { backgroundColor: inputValue }
1082
1149
  : {}
1083
1150
  }
1084
- title={inputValue && inputValue !== 'null' ? `Current colour: ${inputValue}` : 'No colour selected'}
1151
+ title={
1152
+ hasColour
1153
+ ? `Current colour: ${inputValue}`
1154
+ : 'No colour selected'
1155
+ }
1085
1156
  ></div>
1086
1157
  <span className="cpicker-value">
1087
- {inputValue && inputValue !== 'null' ? inputValue : 'No colour selected'}
1158
+ {hasColour ? inputValue : 'No colour selected'}
1088
1159
  </span>
1089
1160
  </div>
1090
- <div className="cpicker-container">
1091
- <Sketch
1092
- color={
1093
- inputValue && inputValue !== 'null'
1094
- ? inputValue
1095
- : '#000000'
1096
- }
1097
- onChange={(color) => {
1098
- onChangeColour(
1099
- { target: { value: color.hex } },
1100
- { hex: color.hex },
1101
- settings.id
1102
- );
1103
- }}
1104
- style={{
1105
- boxShadow: 'none',
1106
- border: 'none'
1107
- }}
1108
- />
1109
- </div>
1161
+ {colorPickerShow && (
1162
+ <div className="cpicker-popover" role="dialog">
1163
+ <Sketch
1164
+ color={hasColour ? inputValue : '#000000'}
1165
+ onChange={(color) => {
1166
+ onChangeColour(
1167
+ { target: { value: color.hex } },
1168
+ { hex: color.hex },
1169
+ settings.id
1170
+ );
1171
+ }}
1172
+ style={{
1173
+ boxShadow: 'none',
1174
+ border: 'none'
1175
+ }}
1176
+ />
1177
+ </div>
1178
+ )}
1110
1179
  </div>
1111
1180
  );
1181
+ }
1112
1182
  case 'copytoclipboard':
1113
1183
  return (
1114
1184
  <>
@@ -807,6 +807,69 @@ form div:has(.react-toggle) input[type='checkbox'] {
807
807
  }
808
808
  }
809
809
 
810
+ /* Popup (click-to-open) variant of the colour picker — the collapsed
811
+ trigger row plus the floating Sketch popover. Additive: the inline
812
+ rules above are left as-is for any consumer still using them. */
813
+ .cpicker-popup {
814
+ position: relative;
815
+ gap: 0;
816
+ }
817
+
818
+ .cpicker-trigger {
819
+ display: flex;
820
+ align-items: center;
821
+ gap: 8px;
822
+ width: 100%;
823
+ box-sizing: border-box;
824
+ min-height: 42px;
825
+ padding: 0.4rem 0.65rem;
826
+ font-size: 0.85rem;
827
+ color: var(--paragraph-color);
828
+ background: #ffffff;
829
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.15);
830
+ border-radius: var(--br, 6px);
831
+ cursor: pointer;
832
+ transition: border 0.15s linear;
833
+ }
834
+
835
+ .cpicker-trigger:hover,
836
+ .cpicker-trigger:focus-visible {
837
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.85);
838
+ outline: none;
839
+ }
840
+
841
+ .cpicker-trigger[aria-disabled='true'] {
842
+ cursor: not-allowed;
843
+ opacity: 0.7;
844
+ }
845
+
846
+ /* The value chip styling only makes sense on the old inline preview —
847
+ inside the trigger it should read as plain input text */
848
+ .cpicker-trigger .cpicker-value {
849
+ background: transparent;
850
+ border: none;
851
+ padding: 0;
852
+ font-weight: 500;
853
+ }
854
+
855
+ .cpicker-popover {
856
+ position: absolute;
857
+ top: calc(100% + 4px);
858
+ left: 0;
859
+ z-index: 1100;
860
+ padding: 8px;
861
+ background: #ffffff;
862
+ border: 1px solid #dee2e6;
863
+ border-radius: 6px;
864
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
865
+ }
866
+
867
+ .cpicker-popover .w-color-sketch {
868
+ box-shadow: none !important;
869
+ border: none !important;
870
+ border-radius: 6px !important;
871
+ }
872
+
810
873
  /* Safari-specific fixes for form elements */
811
874
  @supports (-webkit-touch-callout: none) {
812
875
  /* Safari-specific styles */