Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Prefix instance id when custom node id is passed #325

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
3 changes: 1 addition & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,7 @@ class DropdownTreeSelect extends Component {
}
return
} else if (e.key === 'Backspace' && tags.length && this.searchInput.value.length === 0) {
const lastTag = tags.pop()
this.onCheckboxChange(lastTag._id, false)
this.onTagRemove(tags[tags.length - 1]._id, true)
mrchief marked this conversation as resolved.
Show resolved Hide resolved
} else {
return
}
Expand Down
9 changes: 3 additions & 6 deletions src/tag/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import React, { PureComponent } from 'react'

import './index.css'

export const getTagId = id => `${id}_tag`

class Tag extends PureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
Expand Down Expand Up @@ -37,10 +35,9 @@ class Tag extends PureComponent {
}

render() {
const { id, label, labelRemove = 'Remove', readOnly, disabled } = this.props

const tagId = getTagId(id)
const buttonId = `${id}_button`
const { label, labelRemove = 'Remove', readOnly, disabled, getDOMId } = this.props
const tagId = getDOMId('tag')
const buttonId = getDOMId('button')
const className = ['tag-remove', readOnly && 'readOnly', disabled && 'disabled'].filter(Boolean).join(' ')
const isDisabled = readOnly || disabled

Expand Down
3 changes: 2 additions & 1 deletion src/tags/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import './index.css'

const getTags = (tags = [], onDelete, readOnly, disabled, labelRemove) =>
tags.map(tag => {
const { _id, label, tagClassName, dataset } = tag
const { _id, label, tagClassName, dataset, getDOMId } = tag
return (
<li
className={['tag-item', tagClassName].filter(Boolean).join(' ')}
Expand All @@ -21,6 +21,7 @@ const getTags = (tags = [], onDelete, readOnly, disabled, labelRemove) =>
readOnly={readOnly}
disabled={disabled}
labelRemove={labelRemove}
getDOMId={getDOMId}
/>
</li>
)
Expand Down
14 changes: 4 additions & 10 deletions src/tree-manager/flatten-tree.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import getPartialState from './getPartialState'
import Node from './node'

import { isEmpty } from '../utils'

Expand Down Expand Up @@ -216,16 +217,8 @@ function walkNodes({
_rv = { list: new Map(), defaultValues: [], singleSelectedNode: null },
}) {
const single = simple || radio
nodes.forEach((node, i) => {
node._depth = depth

if (parent) {
node._id = node.id || `${parent._id}-${i}`
node._parent = parent._id
parent._children.push(node._id)
} else {
node._id = node.id || `${rootPrefixId ? `${rootPrefixId}-${i}` : i}`
}
nodes.forEach((n, i) => {
const node = new Node({ rootPrefixId, depth, parent, index: i, ...n })

if (single && node.checked) {
if (_rv.singleSelectedNode) {
Expand Down Expand Up @@ -261,6 +254,7 @@ function walkNodes({
radio,
showPartialState,
hierarchical,
rootPrefixId,
_rv,
})

Expand Down
4 changes: 1 addition & 3 deletions src/tree-manager/keyboardNavigation.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import nodeVisitor from './nodeVisitor'
import { getTagId } from '../tag'

const Keys = {
Up: 'ArrowUp',
Expand Down Expand Up @@ -150,8 +149,7 @@ const getNextFocusAfterTagDelete = (deletedId, prevTags, tags, fallback) => {
if (index < 0 || !tags.length) return fallback

index = tags.length > index ? index : tags.length - 1
const newFocusId = tags[index]._id
const focusNode = document.getElementById(getTagId(newFocusId))
const focusNode = document.getElementById(tags[index].getDOMId('tag'))
if (focusNode) {
return focusNode.firstElementChild || fallback
}
Expand Down
48 changes: 48 additions & 0 deletions src/tree-manager/node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Represents a tree node (not DOM node) in the data tree.
* It can have the following properties:
*
* id {string|optional} User defined id, comes from `data` passed to the component.
* _id {string} Internal id, auto generated if `id` is not defined, otherwise set to `id`.
* rootPrefixId {string} The id of the component's instance.
* parent {Node} Parent node, if a child.
* label {string|required} Checkbox label
* value {string|required} Checkbox value
* children {array<node>|optional} Array of child nodes
* checked {bool|optional} Initial state of checkbox. if true, checkbox is selected and corresponding pill is rendered.
* disabled {bool|optional} Selectable state of checkbox. if true, the checkbox is disabled and the node is not selectable.
* expanded {bool|optional} If true, the node is expanded (children of children nodes are not expanded by default unless children nodes also have expanded: true).
* className {string|optional} Additional css class for the node. This is helpful to style the nodes your way
* tagClassName {string|optional} Css class for the corresponding tag. Use this to add custom style the pill corresponding to the node.
* actions {array<object>|optional} An array of extra action on the node (such as displaying an info icon or any custom icons/elements)
* dataset {object|optional} Allows data-* attributes to be set on the node and tag elements
* isDefaultValue {bool|optional} Indicate if a node is a default value. When true, the dropdown will automatically select the node(s) when there is no other selected node. Can be used on more than one node.
*
*/
export default class Node {
constructor({ depth, rootPrefixId, parent, index, ...dataProps }) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function constructor has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.

// first copy all props coming from data
Object.assign(this, dataProps)

// then assign basic ones
this._depth = depth
this.rootPrefixId = rootPrefixId

if (parent) {
this._id = this.id || `${parent._id}-${index}`
this._parent = parent._id
parent._children.push(this._id)
} else {
this._id = this.id || `${rootPrefixId ? `${rootPrefixId}-${index}` : index}`
}
}

/**
* Given an element, generate a DOM Id that's unique across instances
*/
getDOMId = element => {
// if user has defined id, then ensure it's unique across instances
if (this.id) return `${this.rootPrefixId}_${this.id}_${element}`
return `${this._id}_${element}`
}
}
3 changes: 1 addition & 2 deletions src/trigger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'

import { getAriaLabel } from '../a11y'
import { getTagId } from '../tag'

class Trigger extends PureComponent {
static propTypes = {
Expand All @@ -28,7 +27,7 @@ class Trigger extends PureComponent {
labelledBy.push(triggerId)
}
tags.forEach(t => {
labelledBy.push(getTagId(t._id))
labelledBy.push(t.getDOMId('tag'))
})
labelAttributes = getAriaLabel(texts.label, labelledBy.join(' '))
}
Expand Down