@instructure/ui-drilldown 11.0.1 → 11.0.2-snapshot-1
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/CHANGELOG.md +11 -0
- package/es/Drilldown/DrilldownGroup/props.js +1 -1
- package/es/Drilldown/index.js +106 -33
- package/lib/Drilldown/DrilldownGroup/props.js +1 -1
- package/lib/Drilldown/index.js +106 -33
- package/package.json +18 -18
- package/src/Drilldown/DrilldownGroup/props.ts +7 -0
- package/src/Drilldown/index.tsx +116 -34
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Drilldown/DrilldownGroup/index.d.ts +1 -0
- package/types/Drilldown/DrilldownGroup/index.d.ts.map +1 -1
- package/types/Drilldown/DrilldownGroup/props.d.ts +5 -0
- package/types/Drilldown/DrilldownGroup/props.d.ts.map +1 -1
- package/types/Drilldown/index.d.ts +22 -2
- package/types/Drilldown/index.d.ts.map +1 -1
package/src/Drilldown/index.tsx
CHANGED
|
@@ -26,6 +26,7 @@ import { Children, Component, ReactElement, ReactNode } from 'react'
|
|
|
26
26
|
|
|
27
27
|
import { warn, error } from '@instructure/console'
|
|
28
28
|
import { contains, containsActiveElement } from '@instructure/ui-dom-utils'
|
|
29
|
+
import { deepEqual } from '@instructure/ui-utils'
|
|
29
30
|
import {
|
|
30
31
|
callRenderProp,
|
|
31
32
|
matchComponentTypes,
|
|
@@ -54,6 +55,7 @@ import { withStyle } from '@instructure/emotion'
|
|
|
54
55
|
|
|
55
56
|
import { DrilldownSeparator } from './DrilldownSeparator'
|
|
56
57
|
import { DrilldownOption } from './DrilldownOption'
|
|
58
|
+
import type { DrilldownOptionValue } from './DrilldownOption/props'
|
|
57
59
|
import { DrilldownGroup } from './DrilldownGroup'
|
|
58
60
|
import type { DrilldownGroupProps, GroupChildren } from './DrilldownGroup/props'
|
|
59
61
|
import { DrilldownPage } from './DrilldownPage'
|
|
@@ -147,8 +149,6 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
|
|
|
147
149
|
[optionId: string]: MappedOption
|
|
148
150
|
} = {}
|
|
149
151
|
|
|
150
|
-
// Map of the selected options in groups (grouped by group id)
|
|
151
|
-
|
|
152
152
|
ref: HTMLDivElement | Element | null = null
|
|
153
153
|
|
|
154
154
|
handleRef = (el: HTMLDivElement | Element | null) => {
|
|
@@ -186,7 +186,7 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
|
|
|
186
186
|
activePageId: props.rootPageId,
|
|
187
187
|
highlightedOptionId: undefined,
|
|
188
188
|
lastSelectedId: undefined,
|
|
189
|
-
selectedGroupOptionsMap: this.
|
|
189
|
+
selectedGroupOptionsMap: this.getSelectedGroupOptionsMap()
|
|
190
190
|
}
|
|
191
191
|
this._pageHistory = [props.rootPageId]
|
|
192
192
|
|
|
@@ -206,7 +206,6 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
|
|
|
206
206
|
|
|
207
207
|
componentDidUpdate(_prevProps: DrilldownProps, prevState: DrilldownState) {
|
|
208
208
|
this.props.makeStyles?.(this.makeStylesVariables)
|
|
209
|
-
|
|
210
209
|
if (prevState.activePageId !== this.state.activePageId) {
|
|
211
210
|
// on page change with mouse navigation, some option can get rendered
|
|
212
211
|
// under the cursor and get focused, so we need to wait a tick to see
|
|
@@ -235,6 +234,57 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
|
|
|
235
234
|
highlightedOptionId: undefined
|
|
236
235
|
})
|
|
237
236
|
}
|
|
237
|
+
|
|
238
|
+
// This block syncs the internal state if the selectedOptions prop changes
|
|
239
|
+
// selectedOptions prop values always win over internal state.
|
|
240
|
+
let shouldUpdateState = false
|
|
241
|
+
const nextSelectionMap: SelectedGroupOptionsMap = {
|
|
242
|
+
...this.state.selectedGroupOptionsMap
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.pages.forEach((page) => {
|
|
246
|
+
this.getChildrenArray(page.props.children).forEach((child) => {
|
|
247
|
+
if (matchComponentTypes<GroupChild>(child, [DrilldownGroup])) {
|
|
248
|
+
const {
|
|
249
|
+
id: groupId,
|
|
250
|
+
selectableType,
|
|
251
|
+
selectedOptions,
|
|
252
|
+
children: groupChildren
|
|
253
|
+
} = child.props
|
|
254
|
+
|
|
255
|
+
if (!selectableType || !Array.isArray(selectedOptions)) return
|
|
256
|
+
|
|
257
|
+
const newGroupMap = new Map()
|
|
258
|
+
|
|
259
|
+
this.getChildrenArray(groupChildren)?.forEach((groupChild) => {
|
|
260
|
+
if (
|
|
261
|
+
matchComponentTypes<OptionChild>(groupChild, [DrilldownOption])
|
|
262
|
+
) {
|
|
263
|
+
const { id: optionId, value: optionValue } = groupChild.props
|
|
264
|
+
if (selectedOptions.includes(optionValue)) {
|
|
265
|
+
newGroupMap.set(optionId, optionValue)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const currentGroupMapInState =
|
|
271
|
+
this.state.selectedGroupOptionsMap[groupId] || new Map()
|
|
272
|
+
|
|
273
|
+
if (
|
|
274
|
+
!deepEqual(
|
|
275
|
+
this.getNormalizedMap(newGroupMap),
|
|
276
|
+
this.getNormalizedMap(currentGroupMapInState)
|
|
277
|
+
)
|
|
278
|
+
) {
|
|
279
|
+
nextSelectionMap[groupId] = newGroupMap
|
|
280
|
+
shouldUpdateState = true
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
if (shouldUpdateState) {
|
|
286
|
+
this.setState({ selectedGroupOptionsMap: nextSelectionMap })
|
|
287
|
+
}
|
|
238
288
|
}
|
|
239
289
|
|
|
240
290
|
get makeStylesVariables(): DrilldownStyleProps {
|
|
@@ -346,9 +396,30 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
|
|
|
346
396
|
return id ? this._activeOptionsMap[id] : undefined
|
|
347
397
|
}
|
|
348
398
|
|
|
349
|
-
|
|
350
|
-
|
|
399
|
+
getNormalizedMap(map: Map<string, DrilldownOptionValue>) {
|
|
400
|
+
return Array.from(map.entries())
|
|
401
|
+
}
|
|
351
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Initializes the map of selected options for each group on initial render.
|
|
405
|
+
*
|
|
406
|
+
* This function establishes the selection state based on a clear precedence:
|
|
407
|
+
* 1. **Controlled Mode**: If a `Drilldown.Group` has the `selectedOptions` prop
|
|
408
|
+
* (as an array), it is treated as the absolute source of truth.
|
|
409
|
+
*
|
|
410
|
+
* 2. **Uncontrolled Mode**: If `selectedOptions` is not provided, the selection
|
|
411
|
+
* is determined by `defaultSelected` props, with the following priority:
|
|
412
|
+
* a. The `defaultSelected` boolean on an individual `Drilldown.Option`.
|
|
413
|
+
* b. The `defaultSelected` array of values on the `Drilldown.Group`.
|
|
414
|
+
*
|
|
415
|
+
* It also validates that 'single' selection groups do not mistakenly receive
|
|
416
|
+
* multiple selected or default values.
|
|
417
|
+
*
|
|
418
|
+
* @returns {SelectedGroupOptionsMap} An object where keys are group IDs and
|
|
419
|
+
* values are Maps of the selected { optionId: optionValue } pairs for that group.
|
|
420
|
+
*/
|
|
421
|
+
getSelectedGroupOptionsMap() {
|
|
422
|
+
const selectedGroupOptionsMap: SelectedGroupOptionsMap = {}
|
|
352
423
|
this.pages.forEach((page) => {
|
|
353
424
|
const { children } = page.props
|
|
354
425
|
|
|
@@ -357,31 +428,36 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
|
|
|
357
428
|
const {
|
|
358
429
|
id: groupId,
|
|
359
430
|
selectableType,
|
|
360
|
-
|
|
431
|
+
selectedOptions,
|
|
432
|
+
defaultSelected = [],
|
|
361
433
|
children: groupChildren
|
|
362
434
|
} = child.props
|
|
363
435
|
|
|
364
|
-
if (!selectableType)
|
|
365
|
-
return
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (selectableType && !selectedGroupOptionsMap[groupId]) {
|
|
369
|
-
selectedGroupOptionsMap[groupId] = new Map()
|
|
370
|
-
}
|
|
436
|
+
if (!selectableType) return
|
|
371
437
|
|
|
372
|
-
|
|
373
|
-
error(
|
|
374
|
-
false,
|
|
375
|
-
`Radio type selectable groups can have only one item selected! Group with id "${groupId}" cannot select multiple items: [${groupDefaultSelected.join(
|
|
376
|
-
', '
|
|
377
|
-
)}]!`
|
|
378
|
-
)
|
|
438
|
+
selectedGroupOptionsMap[groupId] = new Map()
|
|
379
439
|
|
|
380
|
-
|
|
440
|
+
if (selectableType === 'single') {
|
|
441
|
+
if (Array.isArray(selectedOptions) && selectedOptions.length > 1) {
|
|
442
|
+
error(
|
|
443
|
+
false,
|
|
444
|
+
`Radio type selectable groups can have only one item selected! Group "${groupId}" has multiple values: [${selectedOptions.join(
|
|
445
|
+
', '
|
|
446
|
+
)}]!`
|
|
447
|
+
)
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
if (defaultSelected.length > 1) {
|
|
451
|
+
error(
|
|
452
|
+
false,
|
|
453
|
+
`Radio type selectable groups can have only one default item selected! Group "${groupId}" has multiple values: [${defaultSelected.join(
|
|
454
|
+
', '
|
|
455
|
+
)}]!`
|
|
456
|
+
)
|
|
457
|
+
return
|
|
458
|
+
}
|
|
381
459
|
}
|
|
382
460
|
|
|
383
|
-
selectedGroupOptionsMap[groupId] = new Map()
|
|
384
|
-
|
|
385
461
|
this.getChildrenArray(groupChildren)?.forEach((groupChild) => {
|
|
386
462
|
if (
|
|
387
463
|
matchComponentTypes<OptionChild>(groupChild, [DrilldownOption])
|
|
@@ -392,18 +468,24 @@ class Drilldown extends Component<DrilldownProps, DrilldownState> {
|
|
|
392
468
|
defaultSelected: optionDefaultSelected
|
|
393
469
|
} = groupChild.props
|
|
394
470
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
471
|
+
if (optionValue == null) return
|
|
472
|
+
|
|
473
|
+
let isSelected = false
|
|
474
|
+
// If the group is controlled via the `selectedOptions` prop, it is the source of truth.
|
|
475
|
+
// `defaultSelected` values are ignored.
|
|
476
|
+
if (Array.isArray(selectedOptions)) {
|
|
477
|
+
isSelected = selectedOptions.includes(optionValue)
|
|
478
|
+
// Second strongest is the `defaultSelected` value on the option itself.
|
|
479
|
+
} else if (optionDefaultSelected === false) {
|
|
480
|
+
isSelected = false
|
|
481
|
+
} else {
|
|
482
|
+
// Last is the `defaultSelected` array on the group.
|
|
483
|
+
const isGroupDefaultSelected =
|
|
484
|
+
defaultSelected.includes(optionValue)
|
|
485
|
+
isSelected = optionDefaultSelected || isGroupDefaultSelected
|
|
398
486
|
}
|
|
399
487
|
|
|
400
|
-
|
|
401
|
-
typeof optionValue !== 'undefined' &&
|
|
402
|
-
groupDefaultSelected
|
|
403
|
-
.filter((value) => typeof value !== 'undefined')
|
|
404
|
-
.includes(optionValue)
|
|
405
|
-
|
|
406
|
-
if (optionDefaultSelected || isGroupDefaultSelected) {
|
|
488
|
+
if (isSelected) {
|
|
407
489
|
selectedGroupOptionsMap[groupId].set(optionId, optionValue)
|
|
408
490
|
}
|
|
409
491
|
}
|