haya-select 1.0.20 → 1.0.21

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haya-select",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -10,7 +10,7 @@ import Option from "./option"
10
10
  import OptionGroup from "./option-group"
11
11
  import PropTypes from "prop-types"
12
12
  import React from "react"
13
- import {anythingDifferent} from "set-state-compare"
13
+ import {anythingDifferent, Shape} from "set-state-compare"
14
14
 
15
15
  const nameForComponentWithMultiple = (component) => {
16
16
  let name = nameForComponent(component)
@@ -51,24 +51,27 @@ export default class HayaSelect extends React.PureComponent {
51
51
  }
52
52
 
53
53
  p = fetchingObject(() => this.props)
54
- s = fetchingObject(() => this.state)
54
+ s = fetchingObject(() => this.shape)
55
55
  t = fetchingObject(this)
56
56
 
57
57
  endOfSelectRef = React.createRef()
58
58
  optionsContainerRef = React.createRef()
59
+ currentSelectedRef = React.createRef()
59
60
  searchTextInputRef = React.createRef()
60
61
 
61
- state = {
62
+ shape = new Shape(this, {
62
63
  currentOptions: this.defaultCurrentOptions(),
63
64
  loadedOptions: this.defaultLoadedOptions(),
64
65
  opened: false,
65
66
  optionsLeft: undefined,
67
+ optionsPlacement: undefined,
66
68
  optionsTop: undefined,
69
+ optionsVisibility: undefined,
67
70
  optionsWidth: undefined,
68
71
  scrollLeft: undefined,
69
72
  scrollTop: undefined,
70
73
  toggled: this.defaultToggled()
71
- }
74
+ })
72
75
 
73
76
  defaultCurrentOptions() {
74
77
  const {defaultValue, defaultValues, values} = this.props
@@ -105,34 +108,40 @@ export default class HayaSelect extends React.PureComponent {
105
108
  if (((defaultValue || defaultValues || defaultValuesFromOptions) || (attribute && model)) && typeof options == "function") {
106
109
  this.loadDefaultValuesFromOptionsCallback()
107
110
  }
111
+
112
+ window.addEventListener("resize", this.t.onAnythingResizedDebounced, true)
113
+ window.addEventListener("scroll", this.t.onAnythingScrolledDebounced, true)
108
114
  }
109
115
 
110
116
  componentDidUpdate() {
111
- this.setState((prevState) => {
112
- const stateUpdate = {}
117
+ const newState = {}
113
118
 
114
- if ("values" in this.props) {
115
- const newCurrentOptions = this.defaultCurrentOptions()
119
+ if ("values" in this.props) {
120
+ const newCurrentOptions = this.defaultCurrentOptions()
116
121
 
117
- if (anythingDifferent(prevState.currentOptions, newCurrentOptions)) {
118
- stateUpdate.currentOptions = newCurrentOptions
119
- }
122
+ if (anythingDifferent(this.shape.currentOptions, newCurrentOptions)) {
123
+ newState.currentOptions = newCurrentOptions
120
124
  }
125
+ }
121
126
 
122
- if ("toggled" in this.props) {
123
- const newToggled = digg(this, "props", "toggled")
127
+ if ("toggled" in this.props) {
128
+ const newToggled = this.p.toggled
124
129
 
125
- if (anythingDifferent(prevState.toggled, newToggled)) {
126
- stateUpdate.toggled = newToggled
127
- }
130
+ if (anythingDifferent(this.shape.toggled, newToggled)) {
131
+ newState.toggled = newToggled
128
132
  }
133
+ }
129
134
 
130
- return stateUpdate
131
- })
135
+ this.shape.set(newState)
136
+ }
137
+
138
+ componentWillUnmount() {
139
+ window.removeEventListener("resize", this.t.onAnythingResizedDebounced)
140
+ window.removeEventListener("scroll", this.t.onAnythingScrolledDebounced)
132
141
  }
133
142
 
134
143
  render() {
135
- const {endOfSelectRef} = this.t
144
+ const {currentSelectedRef, endOfSelectRef} = this.t
136
145
  const {
137
146
  attribute,
138
147
  className,
@@ -150,7 +159,7 @@ export default class HayaSelect extends React.PureComponent {
150
159
  toggleOptions,
151
160
  ...restProps
152
161
  } = this.props
153
- const {currentOptions, opened} = this.s
162
+ const {currentOptions, opened, optionsPlacement} = this.s
154
163
  const BodyPortal = config.getBodyPortal()
155
164
 
156
165
  return (
@@ -168,14 +177,16 @@ export default class HayaSelect extends React.PureComponent {
168
177
  <div
169
178
  className="haya-select-current-selected"
170
179
  data-opened={opened}
171
- onClick={this.onSelectClicked}
180
+ data-options-placement={optionsPlacement}
181
+ onClick={this.t.onSelectClicked}
182
+ ref={currentSelectedRef}
172
183
  >
173
184
  {opened &&
174
185
  <input
175
186
  className="haya-select-search-text-input"
176
- onChange={digg(this, "onSearchTextInputChangedDebounced")}
187
+ onChange={this.tonSearchTextInputChangedDebounced}
177
188
  placeholder={I18n.t("haya_select.search_dot_dot_dot")} type="text"
178
- ref={digg(this, "searchTextInputRef")}
189
+ ref={this.t.searchTextInputRef}
179
190
  />
180
191
  }
181
192
  {!opened &&
@@ -235,18 +246,16 @@ export default class HayaSelect extends React.PureComponent {
235
246
  if (!defaultValues) return
236
247
 
237
248
  const result = await this.props.options({
238
- searchValue: digg(this, "searchTextInputRef", "current")?.value,
249
+ searchValue: digg(this.t.searchTextInputRef, "current")?.value,
239
250
  values: defaultValues
240
251
  })
241
252
 
242
- this.setState((prevState) => ({
243
- currentOptions: prevState.currentOptions.concat(result)
244
- }))
253
+ this.shape.set({currentOptions: this.shape.currentOptions.concat(result)})
245
254
  }
246
255
 
247
256
  loadOptions = async () => {
248
257
  const {options} = this.p
249
- const searchValue = digg(this, "searchTextInputRef", "current")?.value
258
+ const searchValue = digg(this.t.searchTextInputRef, "current")?.value
250
259
 
251
260
  if (Array.isArray(options)) {
252
261
  return this.loadOptionsFromArray(options, searchValue)
@@ -254,7 +263,7 @@ export default class HayaSelect extends React.PureComponent {
254
263
 
255
264
  const loadedOptions = await options({searchValue})
256
265
 
257
- this.setState({loadedOptions})
266
+ this.shape.set({loadedOptions})
258
267
  }
259
268
 
260
269
  hayaSelectOption({key, loadedOption}) {
@@ -263,7 +272,7 @@ export default class HayaSelect extends React.PureComponent {
263
272
  }
264
273
 
265
274
  return <Option
266
- currentOptions={this.state.currentOptions}
275
+ currentOptions={this.shape.currentOptions}
267
276
  icon={this.iconForOption(loadedOption)}
268
277
  key={key}
269
278
  option={loadedOption}
@@ -276,7 +285,7 @@ export default class HayaSelect extends React.PureComponent {
276
285
  const lowerSearchValue = searchValue?.toLowerCase()
277
286
  const loadedOptions = options.filter(({text}) => !lowerSearchValue || text.toLowerCase().includes(lowerSearchValue))
278
287
 
279
- this.setState({loadedOptions})
288
+ this.shape.set({loadedOptions})
280
289
  }
281
290
 
282
291
  onSelectClicked = (e) => {
@@ -292,39 +301,110 @@ export default class HayaSelect extends React.PureComponent {
292
301
  }
293
302
  }
294
303
 
295
- onSearchTextInputChangedDebounced = debounce(digg(this, "loadOptions"), 200)
304
+ onSearchTextInputChangedDebounced = debounce(this.t.loadOptions, 200)
296
305
 
297
306
  closeOptions() {
298
- this.setState({
307
+ this.shape.set({
299
308
  loadedOptions: undefined,
300
309
  opened: false
301
310
  })
302
311
 
303
312
  if (this.props.onOptionsClosed) {
304
- this.props.onOptionsClosed({options: this.state.currentOptions})
313
+ this.props.onOptionsClosed({options: this.shape.currentOptions})
305
314
  }
306
315
  }
307
316
 
308
317
  openOptions() {
318
+ this.setOptionsPositionBelow()
319
+ this.loadOptions()
320
+ this.shape.set(
321
+ {opened: true},
322
+ () => {
323
+ this.focusTextInput()
324
+ this.setOptionsPositionAboveIfOutsideScreen()
325
+ }
326
+ )
327
+ }
328
+
329
+ focusTextInput = () => digg(this.t.searchTextInputRef, "current").focus()
330
+
331
+ setOptionsPosition() {
332
+ this.setOptionsPositionBelow()
333
+ this.setOptionsPositionAboveIfOutsideScreen()
334
+ }
335
+
336
+ setOptionsPositionAboveIfOutsideScreen() {
337
+ const {optionsContainerRef} = this.t
338
+ const {optionsTop} = this.s
339
+ const optionsHeight = digg(optionsContainerRef, "current", "offsetHeight")
340
+ const scrollTop = document.documentElement.scrollTop
341
+ const optionsTotalHeight = optionsHeight + optionsTop + scrollTop
342
+
343
+ if (window.innerHeight < optionsTotalHeight) {
344
+ this.setOptionsPositionAbove()
345
+ } else {
346
+ this.shape.set({optionsVisibility: "visible"})
347
+ }
348
+ }
349
+
350
+ setOptionsPositionAbove() {
351
+ const {optionsContainerRef, currentSelectedRef} = this.t
352
+ const optionsHeight = digg(optionsContainerRef, "current", "offsetHeight")
353
+ const position = currentSelectedRef.current.getBoundingClientRect()
354
+ const {left, top, width} = digs(position, "left", "top", "width")
355
+ const optionsTop = top - optionsHeight + 2
356
+
357
+ this.shape.set(
358
+ {
359
+ opened: true,
360
+ optionsLeft: left,
361
+ optionsPlacement: "above",
362
+ optionsTop,
363
+ optionsVisibility: "visible",
364
+ optionsWidth: width,
365
+ scrollLeft: document.documentElement.scrollLeft,
366
+ scrollTop: document.documentElement.scrollTop
367
+ },
368
+ () => digg(this.t.searchTextInputRef, "current").focus()
369
+ )
370
+ }
371
+
372
+ setOptionsPositionBelow() {
309
373
  const {endOfSelectRef} = this.t
310
374
  const position = endOfSelectRef.current.getBoundingClientRect()
311
375
  const {left, top, width} = digs(position, "left", "top", "width")
312
376
 
313
- this.loadOptions()
314
-
315
- this.setState(
377
+ this.shape.set(
316
378
  {
317
379
  opened: true,
318
380
  optionsLeft: left,
381
+ optionsPlacement: "below",
319
382
  optionsTop: top,
383
+ optionsVisibility: "hidden",
320
384
  optionsWidth: width,
321
385
  scrollLeft: document.documentElement.scrollLeft,
322
386
  scrollTop: document.documentElement.scrollTop
323
387
  },
324
- () => digg(this, "searchTextInputRef", "current").focus()
388
+ () => digg(this.t.searchTextInputRef, "current").focus()
325
389
  )
326
390
  }
327
391
 
392
+ onAnythingResized = (e) => {
393
+ if (this.s.opened) {
394
+ this.setOptionsPosition()
395
+ }
396
+ }
397
+
398
+ onAnythingResizedDebounced = debounce(this.t.onAnythingResized, 25)
399
+
400
+ onAnythingScrolled = (e) => {
401
+ if (this.s.opened) {
402
+ this.setOptionsPosition()
403
+ }
404
+ }
405
+
406
+ onAnythingScrolledDebounced = debounce(this.t.onAnythingScrolled, 25)
407
+
328
408
  onWindowClicked = (e) => {
329
409
  const {optionsContainerRef} = this.t
330
410
  const {opened} = this.s
@@ -337,7 +417,7 @@ export default class HayaSelect extends React.PureComponent {
337
417
 
338
418
  optionsContainer() {
339
419
  const {optionsContainerRef} = this.t
340
- const {loadedOptions, optionsLeft, optionsTop, optionsWidth, scrollLeft, scrollTop} = this.s
420
+ const {loadedOptions, optionsLeft, optionsTop, optionsVisibility, optionsWidth, scrollLeft, scrollTop} = this.s
341
421
 
342
422
  return (
343
423
  <div
@@ -347,6 +427,7 @@ export default class HayaSelect extends React.PureComponent {
347
427
  style={{
348
428
  left: optionsLeft + scrollLeft,
349
429
  top: optionsTop + scrollTop - 1,
430
+ visibility: optionsVisibility,
350
431
  width: optionsWidth,
351
432
  }}
352
433
  >
@@ -372,63 +453,57 @@ export default class HayaSelect extends React.PureComponent {
372
453
 
373
454
  const {onChange, toggleOptions} = this.props
374
455
  const {multiple} = this.p
456
+ const {currentOptions, toggled} = this.shape
457
+ const newState = {}
458
+ const existingOption = currentOptions.find((currentOption) => currentOption.value == loadedOption.value)
459
+ const newToggled = {...toggled}
375
460
 
376
- this.setState(
377
- (prevState) => {
378
- const existingOption = prevState.currentOptions.find((currentOption) => currentOption.value == loadedOption.value)
379
- const newState = {}
380
- const {toggled} = digs(prevState, "toggled")
381
- const newToggled = {...toggled}
461
+ if (existingOption) {
462
+ if (toggleOptions) {
463
+ const currentIndex = digg(toggled, loadedOption.value)
382
464
 
383
- if (existingOption) {
384
- if (toggleOptions) {
385
- const currentIndex = digg(toggled, loadedOption.value)
465
+ if (currentIndex >= (toggleOptions.length - 1)) {
466
+ delete newToggled[loadedOption.value]
386
467
 
387
- if (currentIndex >= (toggleOptions.length - 1)) {
388
- delete newToggled[loadedOption.value]
389
-
390
- newState.currentOptions = prevState.currentOptions.filter((currentOption) => currentOption.value != loadedOption.value)
391
- } else {
392
- newToggled[loadedOption.value] = toggled[loadedOption.value] + 1
393
- }
394
-
395
- newState.toggled = newToggled
396
- } else {
397
- newState.currentOptions = prevState.currentOptions.filter((currentOption) => currentOption.value != loadedOption.value)
398
- }
468
+ newState.currentOptions = currentOptions.filter((currentOption) => currentOption.value != loadedOption.value)
399
469
  } else {
400
- if (toggleOptions) {
401
- newToggled[loadedOption.value] = 0
402
- newState.toggled = newToggled
403
- }
404
-
405
- if (multiple || toggleOptions) {
406
- newState.currentOptions = prevState.currentOptions.concat([loadedOption])
407
- } else {
408
- newState.currentOptions = [loadedOption]
409
- }
470
+ newToggled[loadedOption.value] = toggled[loadedOption.value] + 1
410
471
  }
411
472
 
412
- return newState
413
- },
414
- () => {
415
- if (onChange) {
416
- const toggles = {}
473
+ newState.toggled = newToggled
474
+ } else {
475
+ newState.currentOptions = currentOptions.filter((currentOption) => currentOption.value != loadedOption.value)
476
+ }
477
+ } else {
478
+ if (toggleOptions) {
479
+ newToggled[loadedOption.value] = 0
480
+ newState.toggled = newToggled
481
+ }
417
482
 
418
- for(const toggleKey in this.state.toggled) {
419
- const toggleValue = digg(this, "state", "toggled", toggleKey)
420
- const toggleOption = digg(this, "props", "toggleOptions", toggleValue)
483
+ if (multiple || toggleOptions) {
484
+ newState.currentOptions = currentOptions.concat([loadedOption])
485
+ } else {
486
+ newState.currentOptions = [loadedOption]
487
+ }
488
+ }
421
489
 
422
- toggles[toggleKey] = toggleOption
423
- }
490
+ this.shape.set(newState)
424
491
 
425
- onChange({
426
- options: this.state.currentOptions,
427
- toggles
428
- })
429
- }
492
+ if (onChange) {
493
+ const toggles = {}
494
+
495
+ for(const toggleKey in this.shape.toggled) {
496
+ const toggleValue = digg(this.s.toggled, toggleKey)
497
+ const toggleOption = digg(this.p.toggleOptions, toggleValue)
498
+
499
+ toggles[toggleKey] = toggleOption
430
500
  }
431
- )
501
+
502
+ onChange({
503
+ options: this.shape.currentOptions,
504
+ toggles
505
+ })
506
+ }
432
507
 
433
508
  if (!multiple) this.closeOptions()
434
509
  }
@@ -14,7 +14,11 @@
14
14
  border-radius: 4px;
15
15
  }
16
16
 
17
- &[data-opened="true"] {
17
+ &[data-opened="true"][data-options-placement="above"] {
18
+ border-radius: 0 0 4px 4px;
19
+ }
20
+
21
+ &[data-opened="true"][data-options-placement="below"] {
18
22
  border-radius: 4px 4px 0 0;
19
23
  }
20
24