@startlibs/form 1.0.11 → 1.1.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 (92) hide show
  1. package/lib/AutoComplete.mjs +550 -0
  2. package/lib/AutoComplete.mjs.map +1 -0
  3. package/lib/Checkbox.mjs +76 -0
  4. package/lib/Checkbox.mjs.map +1 -0
  5. package/lib/Combobox.mjs +544 -0
  6. package/lib/Combobox.mjs.map +1 -0
  7. package/lib/ComboboxWithCustom.mjs +77 -0
  8. package/lib/ComboboxWithCustom.mjs.map +1 -0
  9. package/lib/DatePicker.mjs +625 -0
  10. package/lib/DatePicker.mjs.map +1 -0
  11. package/lib/EditableBox.mjs +496 -0
  12. package/lib/EditableBox.mjs.map +1 -0
  13. package/lib/Errors.mjs +176 -0
  14. package/lib/Errors.mjs.map +1 -0
  15. package/lib/ExpandableBox.mjs +209 -0
  16. package/lib/ExpandableBox.mjs.map +1 -0
  17. package/lib/Field.mjs +65 -0
  18. package/lib/Field.mjs.map +1 -0
  19. package/lib/FieldBox.mjs +80 -0
  20. package/lib/FieldBox.mjs.map +1 -0
  21. package/lib/Form.mjs +322 -0
  22. package/lib/Form.mjs.map +1 -0
  23. package/lib/FormValue.mjs +111 -0
  24. package/lib/FormValue.mjs.map +1 -0
  25. package/lib/InputboxGroup.mjs +104 -0
  26. package/lib/InputboxGroup.mjs.map +1 -0
  27. package/lib/Radiobox.mjs +67 -0
  28. package/lib/Radiobox.mjs.map +1 -0
  29. package/lib/RichText.mjs +634 -0
  30. package/lib/RichText.mjs.map +1 -0
  31. package/lib/TextInput.mjs +359 -0
  32. package/lib/TextInput.mjs.map +1 -0
  33. package/lib/alt/checkbox/SimpleCheckbox.mjs +82 -0
  34. package/lib/alt/checkbox/SimpleCheckbox.mjs.map +1 -0
  35. package/lib/alt/checkbox/ToggleCheckbox.mjs +68 -0
  36. package/lib/alt/checkbox/ToggleCheckbox.mjs.map +1 -0
  37. package/lib/alt/radiobox/CleanTabRadiobox.mjs +44 -0
  38. package/lib/alt/radiobox/CleanTabRadiobox.mjs.map +1 -0
  39. package/lib/alt/radiobox/IconRadioBox.mjs +52 -0
  40. package/lib/alt/radiobox/IconRadioBox.mjs.map +1 -0
  41. package/lib/alt/radiobox/SimpleRadiobox.mjs +61 -0
  42. package/lib/alt/radiobox/SimpleRadiobox.mjs.map +1 -0
  43. package/lib/alt/radiobox/TabRadiobox.mjs +54 -0
  44. package/lib/alt/radiobox/TabRadiobox.mjs.map +1 -0
  45. package/lib/enhanceInput.mjs +131 -0
  46. package/lib/enhanceInput.mjs.map +1 -0
  47. package/lib/index.cjs +5345 -0
  48. package/lib/index.cjs.map +1 -0
  49. package/lib/index.mjs +28 -0
  50. package/lib/index.mjs.map +1 -0
  51. package/lib/useConfirmDialog.mjs +117 -0
  52. package/lib/useConfirmDialog.mjs.map +1 -0
  53. package/lib/useDraftConfirmation.mjs +56 -0
  54. package/lib/useDraftConfirmation.mjs.map +1 -0
  55. package/lib/validation.mjs +91 -0
  56. package/lib/validation.mjs.map +1 -0
  57. package/lib/withFormImageInput.mjs +281 -0
  58. package/lib/withFormImageInput.mjs.map +1 -0
  59. package/package.json +44 -25
  60. package/src/AutoComplete.jsx +642 -0
  61. package/src/Checkbox.jsx +45 -0
  62. package/src/Combobox.jsx +606 -0
  63. package/src/ComboboxWithCustom.jsx +68 -0
  64. package/src/DatePicker.jsx +733 -0
  65. package/src/EditableBox.jsx +502 -0
  66. package/src/Errors.jsx +174 -0
  67. package/src/ExpandableBox.jsx +207 -0
  68. package/src/Field.jsx +76 -0
  69. package/src/FieldBox.jsx +136 -0
  70. package/src/Form.jsx +291 -0
  71. package/src/FormValue.jsx +67 -0
  72. package/src/IconRadioBoxOldHubt.jsx +93 -0
  73. package/src/InputboxGroup.jsx +121 -0
  74. package/src/Radiobox.jsx +33 -0
  75. package/src/RichText.jsx +812 -0
  76. package/src/TextInput.jsx +519 -0
  77. package/src/alt/checkbox/SimpleCheckbox.jsx +162 -0
  78. package/src/alt/checkbox/ToggleCheckbox.jsx +143 -0
  79. package/src/alt/radiobox/CleanTabRadiobox.jsx +46 -0
  80. package/src/alt/radiobox/IconRadioBox.jsx +93 -0
  81. package/src/alt/radiobox/SimpleRadiobox.jsx +153 -0
  82. package/src/alt/radiobox/TabRadiobox.jsx +73 -0
  83. package/src/enhanceInput.jsx +107 -0
  84. package/src/index.js +27 -0
  85. package/src/useConfirmDialog.jsx +105 -0
  86. package/src/useDraftConfirmation.jsx +60 -0
  87. package/src/validation.js +92 -0
  88. package/src/withFormImageInput.jsx +297 -0
  89. package/.babelrc +0 -28
  90. package/lib/index.js +0 -6255
  91. package/rollup.config.js +0 -43
  92. package/yarn-error.log +0 -1607
@@ -0,0 +1,606 @@
1
+ import React, {useRef, useMemo, useEffect} from 'react'
2
+ import _ from 'lodash/fp'
3
+ import styled,{css} from 'styled-components'
4
+ import {FixedSizeList} from "react-window";
5
+ import {ListItem, ContextListMenu, Icon, Loading} from '@startlibs/components'
6
+ import {Fill, useUpdateEffect, useLazyConstant, useToggle, useRefState, useEnsureRef} from '@startlibs/core'
7
+ import {normalizeText, getUID, callIfFunction, getColor, media, ifUndefined} from '@startlibs/utils'
8
+
9
+ import {Field} from './Field'
10
+ import {TextInput} from './TextInput'
11
+ import {useInput} from './enhanceInput'
12
+ import {getDialogLayersSize, ListSeparator, usePositioning, willUseIsSticky} from '@startlibs/components'
13
+
14
+ export const ComboboxMenu = styled(ContextListMenu)`
15
+ z-index: 100;
16
+ background-color: white;
17
+ border-radius: 4px 4px 6px 6px;
18
+ margin-top: -1px;
19
+ margin-bottom: 0.5rem;
20
+ box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 10px -2px rgba(0,0,0,0.15), 0 1px 2px 0px rgba(0,0,0,0.2);
21
+ border: 1px solid #b4b4b4;
22
+ max-height: 25rem;
23
+ width: auto;
24
+ ${ListItem} > * {
25
+ font-size: 13px;
26
+ }
27
+ ${media.mobile`
28
+ ${ListItem} > * {
29
+ font-size: 14px;
30
+ }
31
+ `}
32
+ ${props => props.withoutIcons && css`
33
+ ${ListItem} > * {
34
+ padding-left: 1.25rem;
35
+ padding-right: 1.25rem;
36
+ }
37
+ `}
38
+ ${props => props.withWindowing && `
39
+ overflow: hidden;
40
+ padding: 0;
41
+ `}
42
+ `
43
+ export const ComboboxInput = styled.div `
44
+ width: 100%;
45
+ font-size: 14px;
46
+ border: 1px solid ${getColor('gray210')};
47
+ border-radius: 8px;
48
+ color: ${getColor('gray60')};
49
+ background: white;
50
+ position: relative;
51
+ display: block;
52
+ min-height: 3rem;
53
+ padding: 0.5rem 2.5rem 0.5rem 0.7rem;
54
+ overflow: auto;
55
+ outline: 0;
56
+ outline: none;
57
+ cursor: pointer;
58
+ user-select: none;
59
+ overflow: hidden;
60
+ display: flex;
61
+ align-items: center;
62
+ word-break: break-word;
63
+ ${props => props.suggestive &&`
64
+ padding-right: 0.7rem;
65
+ `}
66
+ ${media.mobile`
67
+ min-height: 42px;
68
+ padding-left: 1rem;
69
+ ${props => props.suggestive &&`
70
+ padding-right: 1rem;
71
+ `}
72
+ `}
73
+ ${props => props.isFocused && css`
74
+ border-color: ${getColor('gray180')};
75
+ box-shadow: 0px 0px 0px 2px rgba(0,0,0,0.1);
76
+ `}
77
+ :focus {
78
+ border-color: ${getColor('gray180')};
79
+ box-shadow: 0px 0px 0px 2px rgba(0,0,0,0.1);
80
+ }
81
+ ${props => props.hasErrors && css`
82
+ border-color: ${getColor('alert')} !important;
83
+ `}
84
+ input {
85
+ position: absolute;
86
+ top: -1px;
87
+ left: 0;
88
+ border: none;
89
+ ${props => !props.suggestive &&`
90
+ padding-right: 2.5rem;
91
+ `}
92
+ }
93
+ `
94
+
95
+ const ComboboxDropdownIcon = styled(Icon)`
96
+ position: absolute;
97
+ top: 51%;
98
+ transform: translateY(-50%);
99
+ right: 1rem;
100
+ color: ${getColor('gray60')};
101
+ `
102
+ const FooterOptionsContainer = styled.div`
103
+ background: white;
104
+ border-top: 1px solid rgba(0,0,0,0.1);
105
+ padding: 0;
106
+ ${props => !props.hasWindoning && `
107
+ padding: 0.5rem 0;
108
+ margin: 0.5rem 0 -0.5rem;
109
+ bottom: -0.5rem;
110
+ position: sticky;
111
+ `}
112
+ `
113
+ const ComboboxPlaceholder = styled.div`
114
+ color: rgba(0,0,0,0.3);
115
+ `
116
+
117
+ const ComboboxLoading = styled(Loading)`
118
+ bottom: 6px;
119
+ right: 0.6rem;
120
+ left: auto;
121
+ top: auto;
122
+ ${media.mobile`
123
+ bottom: 9px;
124
+ `}
125
+ `
126
+
127
+ export const SEPARATOR = {}
128
+ export const NO_DIGITABLE_FOUND = "Nenhum resultado encontrado"
129
+ //shouldRecenter?
130
+ export const Combobox = styled(React.forwardRef((
131
+ {
132
+ getLabel = _.identity,
133
+ getLabelAs,
134
+ getBoxLabelAs,
135
+ getIcon,
136
+ scrollContainer = (el) => el.closest('.dialog-container') || document.body,
137
+ className,
138
+ options,
139
+ autoFocus,
140
+ autoOpenMenu,
141
+ mandatory,
142
+ placeholder,
143
+ disabled,
144
+ fieldValue,
145
+ label,
146
+ path,
147
+ withWindowing,
148
+ tabIndex = 1,
149
+ fieldClassName,
150
+ descText,
151
+ helpText,
152
+ raw,
153
+ digitable,
154
+ digitableConstraint,
155
+ digitableAsOption,
156
+ suggestive,
157
+ onDigitableChange,
158
+ footerOptions,
159
+ isLoading,
160
+ isOptionDisabled,
161
+ menuRef,
162
+ overrideSetValue,
163
+ transformOnSelect = _.identity,
164
+ check = (a,b) => a === b,
165
+ ...rest
166
+ },ref
167
+ ) => {
168
+
169
+ const selectedElement = useRef()
170
+ const selectedElementIndex = useRef()
171
+ const fieldRef = useRef()
172
+ const windowingRef = useRef()
173
+ const skipFocus = useRefState(autoFocus)
174
+ const skipHover = useRefState()
175
+ const skipDigitableSetValue = useRefState()
176
+ const listElement = useEnsureRef(menuRef)
177
+ const container = useEnsureRef(ref)
178
+ const keyState = useRefState()
179
+
180
+ const contextMenu = useToggle(false)
181
+ const hover = useToggle()
182
+ const strongHover = useRefState()
183
+ const scrollToVisibility = useToggle()
184
+ const autoCompleteUID = useLazyConstant(() => getUID())
185
+ const [formValue, confirmValue, {hasErrors}] = useInput({fieldValue, path,...rest},raw)
186
+ const value = useToggle(formValue)
187
+ const setValue = (v) => {
188
+ value.openWith(v)
189
+ }
190
+ const digitableValue = useToggle(getLabel(value.isOpen))
191
+ const digitableRef = useRef()
192
+ const ignoringFilter = useToggle()
193
+
194
+ const setHover = (v) => {
195
+ hover.openWith(v)
196
+ if (digitable) {
197
+ strongHover.set(hover.get())
198
+ } else {
199
+ setValue(v)
200
+ }
201
+ }
202
+
203
+ React.useLayoutEffect(() => {
204
+ if (digitable && !skipDigitableSetValue.get()) {
205
+ if (contextMenu.isOpen) {
206
+ digitableRef.current.select()
207
+ if (value.get() && !suggestive) {
208
+ setHover(transformOnSelect(value.get()))
209
+ }
210
+ } else {
211
+ digitableValue.openWith(getLabel(value.get()))
212
+ strongHover.set(hover.get())
213
+ hover.close()
214
+ }
215
+ }
216
+ skipDigitableSetValue.set(false)
217
+ }, [contextMenu.isOpen])
218
+ useEffect(() => { contextMenu.isOpen && recenterScroll() }, [contextMenu.isOpen])
219
+
220
+ useEffect(() => { selectedElement.current && scrollToVisibility.isOpen && makeOptionVisible() },[hover])
221
+ useEffect(() => {autoFocus && container.current?.focus()},[])
222
+ useEffect(() => {autoOpenMenu && toggleCombobox()},[])
223
+ useUpdateEffect(() => {
224
+ if (value.isOpen !== formValue && !contextMenu.isOpen) {
225
+ confirmValue(value.isOpen)
226
+ }},[!!contextMenu.isOpen])
227
+ useEffect(() => {
228
+ if (formValue !== value.isOpen) {
229
+ setValue(formValue)
230
+ }
231
+ if (digitable) {
232
+ digitableValue.openWith(prev => prev !== getLabel(formValue) ? getLabel(formValue) : prev)
233
+ }
234
+ },[formValue])
235
+
236
+ const filteredOptions = useMemo(() =>
237
+ _.flow(
238
+ _.filter(v => {
239
+ if (!digitable || !digitableValue.isOpen || ignoringFilter.isOpen) {
240
+ return true
241
+ }
242
+ return normalizeText((getLabel(v))).includes(normalizeText(digitableValue.isOpen))
243
+ }),
244
+ _.sortBy(v => normalizeText(getLabel(v)).indexOf(normalizeText(digitableValue.isOpen))>0)
245
+ )(options)
246
+ ,[options,digitable, digitableValue.isOpen, ignoringFilter.isOpen])
247
+ const digitableOption = useMemo(() => {
248
+ const option= filteredOptions.length < 8 && digitableAsOption && !!digitableValue.isOpen && callIfFunction(digitableAsOption, digitableValue.isOpen)
249
+ return option || (!filteredOptions.length && digitable && !suggestive && NO_DIGITABLE_FOUND)
250
+ },
251
+ [digitableValue.isOpen, filteredOptions.length]
252
+ )
253
+
254
+ const availableOptions = filteredOptions.concat(digitableOption || [])
255
+
256
+ const allOptions = availableOptions.concat(footerOptions || [])
257
+ .filter((v) => v !== SEPARATOR)
258
+
259
+
260
+ const withPlaceholder = !value.isOpen && value.isOpen !== 0 && placeholder
261
+ useUpdateEffect(() => {
262
+ if (suggestive || !digitable) { return }
263
+ if (hover.isOpen) {
264
+ setHover(allOptions[0] === NO_DIGITABLE_FOUND ? allOptions[1] : allOptions[0])
265
+ } else if (digitableValue.isOpen) {
266
+ setHover(ifUndefined(value.isOpen,allOptions[0]))
267
+ }
268
+ },[digitableValue.isOpen])
269
+
270
+ const toggleCombobox = () => {
271
+ if (contextMenu.isOpen) {
272
+ contextMenu.close()
273
+ } else {
274
+ contextMenu.open()
275
+ }
276
+ hover.close()
277
+ }
278
+
279
+ const getCurrentHeight = () => listElement.current.offsetHeight - (listElement.current.querySelector(".JS-list-footer")?.offsetHeight || 0)
280
+
281
+ const recenterScroll = () => {
282
+ if (withWindowing) {
283
+ const index = hover.get() ? availableOptions.indexOf(hover.get()) : selectedElementIndex.current
284
+ if (windowingRef.current) {
285
+ windowingRef.current.scrollToItem(_.isFinite(index) ? index : availableOptions.map(getLabel).indexOf(getLabel(value.get())) || 0, "center")
286
+ }
287
+ } else {
288
+ if (!listElement.current) { return }
289
+ if (listElement.current.getBoundingClientRect().bottom > window.innerHeight) {
290
+ const currentScrollContainer = scrollContainer(container.current)
291
+ currentScrollContainer.scrollTop = listElement.current.getBoundingClientRect().bottom + currentScrollContainer.scrollTop - window.innerHeight
292
+ }
293
+ if (selectedElement.current) {
294
+ listElement.current.scrollTop = selectedElement.current.offsetTop + (selectedElement.current.offsetHeight / 2) - (getCurrentHeight() / 2)
295
+ }
296
+ }
297
+ }
298
+
299
+ const makeOptionVisible = () => {
300
+ if (withWindowing) {
301
+ if (windowingRef.current) {
302
+ windowingRef.current.scrollToItem(selectedElementIndex.current || 0)
303
+ }
304
+ } else {
305
+ if ((footerOptions && footerOptions?.indexOf(hover.isOpen)>=0) || !listElement.current) {
306
+ return
307
+ } else if (selectedElement.current.offsetTop < listElement.current.scrollTop) {
308
+ skipHover.set(true)
309
+ listElement.current.scrollTop = selectedElement.current.offsetTop
310
+ } else if (selectedElement.current.offsetTop + selectedElement.current.offsetHeight > listElement.current.scrollTop + getCurrentHeight()) {
311
+ skipHover.set(true)
312
+ listElement.current.scrollTop = selectedElement.current.offsetTop + selectedElement.current.offsetHeight - getCurrentHeight()
313
+ }
314
+ }
315
+ }
316
+
317
+ const handleHover = (value,e) => {
318
+ if (skipHover.get()) {
319
+ skipHover.set(false)
320
+ return
321
+ }
322
+ scrollToVisibility.close()
323
+ hover.openWith(value)
324
+ }
325
+ const digitableKeyDown = (e) => {
326
+ if(e.key.length == 1 || e.key == "Spacebar") {
327
+ e.stopPropagation()
328
+ } else if (e.key === 'Tab' || e.key === 'Enter') {
329
+ if (suggestive && allOptions.indexOf(strongHover.get())<0) {
330
+ if (allOptions.indexOf(value.isOpen)>=0) {
331
+ setValue(transformOnSelect(value.isOpen))
332
+ } else {
333
+ setValue(transformOnSelect(callIfFunction(digitableAsOption,digitableValue.get()) || digitableValue.get()))
334
+ }
335
+ } else if (strongHover.get() === hover.get()) {
336
+ setValue(transformOnSelect(strongHover.get()))
337
+ }
338
+ if (e.key === "Enter") {
339
+ skipFocus.set(true);
340
+ container.current.focus()
341
+ }
342
+ }
343
+ }
344
+
345
+ const keyDown = (e) => {
346
+ if (e.getModifierState('Shift') && e.key === 'Tab') { return; }
347
+ if ((e.key === 'Escape' || e.key === 'Tab') && contextMenu.isOpen) {
348
+ e.stopPropagation()
349
+ toggleCombobox()
350
+ }
351
+ if (e.key.length === 1) {
352
+ e.stopPropagation()
353
+ if (!contextMenu.isOpen) {
354
+ contextMenu.open()
355
+ if (digitable) {
356
+ skipDigitableSetValue.set(true)
357
+ digitableValue.openWith(e.key)
358
+ return
359
+ }
360
+ }
361
+ const prevKeyState = keyState.get()
362
+ const repeated = !prevKeyState || (e.key === prevKeyState.word[0] && prevKeyState.repeated)
363
+ const word = (prevKeyState?.word||'')+e.key
364
+ clearTimeout(prevKeyState?.timeout || -1)
365
+ const timeout = setTimeout(() => keyState.set(null),1000)
366
+ keyState.set({repeated,word,timeout})
367
+
368
+ const labels = allOptions.map(o => {
369
+ const label = getLabel(o)
370
+ return _.isString(label) ? label.slice(0,repeated ? 1 : word.length).toLowerCase() : ""
371
+ })
372
+ const goTo = (index) => {
373
+ scrollToVisibility.open()
374
+ setHover(allOptions[index])
375
+ }
376
+
377
+ const currentIndex = allOptions.indexOf(hover.isOpen !== false ? hover.isOpen : value.isOpen)
378
+
379
+ const direction = repeated ? (e.shiftKey ? -1 : 1) : 0
380
+ const fnName = direction < 0 ? 'lastIndexOf' : 'indexOf'
381
+ const item = repeated ? word.slice(0,1) : word
382
+ const fromIndex = (currentIndex || (direction < 0 ? labels.length - 1 : 0)) + direction
383
+ const next = labels[fnName](item,fromIndex)
384
+ if (next >= 0) {
385
+ goTo(next)
386
+ } else {
387
+ const next = labels[fnName](item)
388
+ if (next >= 0) {
389
+ goTo(next)
390
+ }
391
+ }
392
+ }
393
+ if (['ArrowUp', 'ArrowDown', ' ', 'Enter'].indexOf(e.key) < 0) {
394
+ return
395
+ }
396
+ e.stopPropagation()
397
+ e.preventDefault()
398
+ if (contextMenu.isOpen) {
399
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
400
+ const oldValue = hover.isOpen !== false ? hover.isOpen : value.isOpen
401
+ const previousIndex = allOptions.indexOf(oldValue)
402
+ const newIndex = Math.min(Math.max((previousIndex + allOptions.length + (e.key === 'ArrowUp' ? (previousIndex < 0 ? 0 : -1) : 1)) % allOptions.length, 0), allOptions.length - 1)
403
+ scrollToVisibility.open()
404
+ setHover(allOptions[newIndex])
405
+ } else {
406
+ if (hover.isOpen !== false) {
407
+ setValue(transformOnSelect(hover.isOpen))
408
+ }
409
+ toggleCombobox()
410
+ }
411
+ } else {
412
+ toggleCombobox()
413
+ }
414
+ }
415
+
416
+ const createOption = (option,i,props,opts) => {
417
+ if (option === SEPARATOR) {
418
+ return <ListSeparator key={"separator"+i}/>
419
+ }
420
+ if (option === NO_DIGITABLE_FOUND) {
421
+ return <Option
422
+ key={NO_DIGITABLE_FOUND+i}
423
+ disabled
424
+ label={NO_DIGITABLE_FOUND}
425
+ />
426
+ }
427
+ const label = getLabel(option,{option:true})
428
+ const checked = hover.isOpen !== false ? check(option,hover.isOpen) : check(option,value.isOpen)
429
+ if (checked) {
430
+ selectedElementIndex.current = i
431
+ }
432
+ return <Option
433
+ key={label+i}
434
+ option={option}
435
+ disabled={callIfFunction(isOptionDisabled,option,label)}
436
+ icon={callIfFunction(getIcon,option)}
437
+ checked={checked}
438
+ label={getLabelAs ? getLabelAs(option) : label}
439
+ setValue={(v) => {
440
+ if (!callIfFunction(overrideSetValue,v,{setValue,close:contextMenu.close})) {
441
+ setValue(transformOnSelect(v));
442
+ digitableValue.openWith(getLabel(v))
443
+ contextMenu.close()
444
+ };
445
+ skipFocus.set(true);
446
+ container.current.focus()
447
+ }
448
+ }
449
+ setRef={selectedElement}
450
+ onHover={handleHover}
451
+ {...props}
452
+ />
453
+ }
454
+ const onClickInput = () => {
455
+ if (digitableRef.current && !contextMenu.get()) {
456
+ digitableRef.current.select()
457
+ }
458
+ if (!suggestive) {
459
+ ignoringFilter.open()
460
+ }
461
+ contextMenu.open()
462
+ }
463
+ const onFocus = (e) => {
464
+ if(listElement.current?.contains(e.target) || skipFocus.get()) {
465
+ skipFocus.set(false)
466
+ } else {
467
+ if (!suggestive) {
468
+ ignoringFilter.open()
469
+ }
470
+ contextMenu.open()
471
+ }
472
+ }
473
+
474
+ const onBlur = (e) => {
475
+ if(!listElement.current?.contains(e.relatedTarget)) {
476
+ if (!availableOptions.length && digitableAsOption && digitableValue.get()) {
477
+ setValue(transformOnSelect(callIfFunction(digitableAsOption,digitableValue.get())))
478
+ } else if (suggestive) {
479
+ setValue(transformOnSelect(callIfFunction(digitableAsOption,digitableValue.get()) || digitableValue.get()))
480
+ } else if (formValue !== value.isOpen) {
481
+ confirmValue(value.isOpen)
482
+ }
483
+ contextMenu.close()
484
+ }
485
+ }
486
+
487
+ return <Field
488
+ className={fieldClassName}
489
+ mandatory={mandatory}
490
+ descText={descText}
491
+ helpText={helpText}
492
+ label={label}
493
+ labelClick={toggleCombobox}
494
+ ref={fieldRef}
495
+ >
496
+ <ComboboxInput
497
+ onBlur={onBlur}
498
+ onFocus={onFocus}
499
+ onClick={onClickInput}
500
+ ref={container}
501
+ tabIndex={contextMenu.isOpen && digitable ? '-1' : tabIndex}
502
+ onKeyDown={keyDown}
503
+ disabled={disabled}
504
+ withPlaceholder={withPlaceholder}
505
+ hasErrors={hasErrors}
506
+ comboboxIsOpen={contextMenu.isOpen}
507
+ isFocused={digitable && !!contextMenu.isOpen}
508
+ suggestive={suggestive}
509
+ >
510
+ {(digitable && !!contextMenu.isOpen) ?
511
+ <TextInput
512
+ raw
513
+ tabIndex={tabIndex}
514
+ autoFocus
515
+ autoComplete={autoCompleteUID}
516
+ ref={digitableRef}
517
+ constraint={digitableConstraint}
518
+ onKeyDown={digitableKeyDown}
519
+ setValue={(v) => {ignoringFilter.close();digitableValue.openWith(v)}}
520
+ value={digitableValue.isOpen || ""}
521
+ placeholder={placeholder}
522
+ onChange={onDigitableChange}
523
+ />
524
+ : withPlaceholder ?
525
+ <ComboboxPlaceholder>{placeholder}</ComboboxPlaceholder>
526
+ : <div>{(getBoxLabelAs ? getBoxLabelAs(value.isOpen) : getLabel(value.isOpen))}</div>
527
+ }
528
+ {isLoading ? <ComboboxLoading absolute size={20}/> : !suggestive && <ComboboxDropdownIcon icon="arrow-down"/>}
529
+ {
530
+ !!contextMenu.isOpen && (!suggestive || !digitable || (allOptions.length && digitableValue.isOpen)) &&
531
+ <ComboboxMenuContainer
532
+ withWindowing={withWindowing}
533
+ ref={listElement}
534
+ windowingRef={windowingRef}
535
+ options={availableOptions}
536
+ digitableValue={digitableValue.get()}
537
+ digitableAsOption={digitableAsOption}
538
+ footerOptions={footerOptions}
539
+ getIcon={getIcon}
540
+ createOption={createOption}
541
+ />
542
+ }
543
+ </ComboboxInput>
544
+ </Field>
545
+
546
+ }))`
547
+ user-select: none;
548
+ `
549
+ const [WithIsSticky] = willUseIsSticky()
550
+
551
+ export const Option = ({setValue, icon, option, onHover, checked, label, setRef, ...props}) =>
552
+ <ListItem
553
+ hover={checked}
554
+ tabIndex={-1}
555
+ onMouseOver={(e) => onHover(option,e)}
556
+ ref={checked ? setRef : undefined}
557
+ onClick={(e) => {e.stopPropagation(); setValue(option)}}
558
+ {...props}
559
+ >
560
+ <a>
561
+ {icon && <Icon icon={icon}/>}
562
+ {label}
563
+ </a>
564
+ </ListItem>
565
+
566
+ const ITEM_HEIGHT = 37
567
+ const ComboboxMenuContainer = React.forwardRef(({options,digitableAsOption,footerOptions,getIcon, createOption,withWindowing, digitableValue, windowingRef},ref) => {
568
+ const [positionInfo, positionPlaceholder] = usePositioning(ref, {fill:true,on:options.length})
569
+ const getMenuHeight = (options) => Math.min((options.length * ITEM_HEIGHT) + 9, 300 - ((footerOptions?.length || 0) * ITEM_HEIGHT) - (footerOptions?.length ? 7 : 0))
570
+ const slot = 'Popup'
571
+ const slotName = useMemo(() => getDialogLayersSize() === 0 ? slot : (slot + '-' + (getDialogLayersSize() - 1)), [])
572
+
573
+ return <>
574
+ {positionPlaceholder}
575
+ <Fill name={slotName}>
576
+ <ComboboxMenu
577
+ {...positionInfo}
578
+ ref={ref}
579
+ withWindowing={withWindowing}
580
+ withoutIcons={!getIcon}
581
+ >
582
+ {withWindowing
583
+ ? <FixedSizeList
584
+ ref={windowingRef}
585
+ height={getMenuHeight(options)}
586
+ itemCount={options.length}
587
+ itemSize={ITEM_HEIGHT}
588
+ width="100%"
589
+ >{({index, style}) =>
590
+ createOption(options[index],index,{style})
591
+ }</FixedSizeList>
592
+ : options.map(createOption).filter(Boolean)
593
+ }
594
+ {
595
+ footerOptions &&
596
+ <WithIsSticky intersection>{(sentinel,isSticky) => <>
597
+ <FooterOptionsContainer isSticky={isSticky} className="JS-list-footer" hasWindoning={withWindowing}>
598
+ {footerOptions.map(createOption)}
599
+ </FooterOptionsContainer>
600
+ {sentinel("margin-top:-3px;")}
601
+ </>}</WithIsSticky>
602
+ }
603
+ </ComboboxMenu>
604
+ </Fill>
605
+ </>
606
+ })
@@ -0,0 +1,68 @@
1
+ import React, {useLayoutEffect} from 'react'
2
+ import {usePrevious, useToggle} from '@startlibs/core'
3
+ import {callIfFunction} from '@startlibs/utils'
4
+ import {Combobox} from './Combobox'
5
+ import {useFormMeta, useFormValue} from './Form'
6
+ import {TextInput} from './TextInput'
7
+ import _ from "lodash/fp";
8
+
9
+ //shouldRecenter?
10
+ export const ComboboxWithCustom = ({options,label,constraint, path, getLabel = _.identity, getBoxLabelAs = getLabel, valueOnCustom = _.identity,customIndex = options.length - 1, tabIndex = 1, ...extra}) => {
11
+ const customValue = useToggle()
12
+ const [value,setValue] = useFormValue(path)
13
+ const [meta,setMeta] = useFormMeta(["onCustom"].concat(_.toPath(path)))
14
+ const prevTitle = usePrevious(value)
15
+
16
+ useLayoutEffect(() => {
17
+ if (value === options[customIndex]) {
18
+ setMeta(true)
19
+ customValue.openWith('')
20
+ setValue(callIfFunction(valueOnCustom,prevTitle))
21
+ }
22
+ }, [value])
23
+
24
+ const updateCustomValue = e => {
25
+ if (e.type === 'keydown' && e.keyCode === 27) {
26
+ e.stopPropagation()
27
+ setMeta(false)
28
+ customValue.close()
29
+ } else if (
30
+ (e.type === 'keydown' && e.keyCode === 13) ||
31
+ e.type === 'blur'
32
+ ) {
33
+ e.stopPropagation()
34
+ if (customValue.isOpen) {
35
+ setValue(customValue.isOpen)
36
+ }
37
+ setMeta(false)
38
+ customValue.close()
39
+ }
40
+ }
41
+
42
+ const titleOptions = (!value || options.indexOf(value) >= 0) ? options : [value].concat(options)
43
+ return customValue.isOpen !== false ?
44
+ <TextInput
45
+ label={label}
46
+ tabIndex={tabIndex}
47
+ autoFocus
48
+ value={customValue.isOpen}
49
+ setValue={customValue.openWith}
50
+ maxLength={70}
51
+ onBlur={updateCustomValue}
52
+ onKeyPress={updateCustomValue}
53
+ onKeyDown={updateCustomValue}
54
+ constraint={constraint}
55
+ raw
56
+ {...extra}
57
+ /> :
58
+ <Combobox
59
+ getBoxLabelAs={v => v === options[customIndex] ? '' : getBoxLabelAs(v)}
60
+ label={label}
61
+ path={path}
62
+ onChange={setValue}
63
+ options={titleOptions}
64
+ tabIndex={tabIndex}
65
+ getLabel={getLabel}
66
+ {...extra}
67
+ />
68
+ }