teratail header banner
teratail header banner
質問するログイン新規登録

質問編集履歴

2

記述したファイルに誤りがあり、修正

2021/12/15 00:23

投稿

hayatoganbaru
hayatoganbaru

スコア7

title CHANGED
File without changes
body CHANGED
@@ -16,167 +16,114 @@
16
16
 
17
17
 
18
18
  ```
19
+ import { createContext, mergeRefs } from "@chakra-ui/react-utils"
20
+ import { RefCallback, useRef, useState } from "react"
21
+ import { DescendantsManager, DescendantOptions } from "./descendant"
19
- import { sortNodes, isElement, getNextIndex, getPrevIndex } from "./utils"
22
+ import { useSafeLayoutEffect, cast } from "./utils"
20
23
 
21
- export type DescendantOptions<T = {}> = T & {
22
- /**
24
+ /**
25
+ * @internal
23
- * If `true`, the item will be registered in all nodes map
26
+ * React hook that initializes the DescendantsManager
24
- * but omitted from enabled nodes map
25
- */
27
+ */
28
+ function useDescendants<T extends HTMLElement = HTMLElement, K = {}>() {
26
- disabled?: boolean
29
+ const descendants = useRef(new DescendantsManager<T, K>())
27
- /**
28
- * The id of the item
30
+ useSafeLayoutEffect(() => {
31
+ return () => descendants.current.destroy()
29
- */
32
+ })
30
- id?: string
33
+ return descendants.current
31
34
  }
32
35
 
36
+ export interface UseDescendantsReturn
33
- export type Descendant<T, K> = DescendantOptions<K> & {
37
+ extends ReturnType<typeof useDescendants> {}
34
- /**
35
- * DOM element of the item
36
- */
37
- node: T
38
- /**
39
- * index of item in all nodes map and enabled nodes map
40
- */
41
- index: number
42
- }
43
38
 
39
+ /* -------------------------------------------------------------------------------------------------
40
+ * Descendants context to be used in component-land.
41
+ - Mount the `DescendantsContextProvider` at the root of the component
42
+ - Call `useDescendantsContext` anywhere you need access to the descendants information
43
+
44
+ NB: I recommend using `createDescendantContext` below
45
+ * -----------------------------------------------------------------------------------------------*/
46
+
47
+ const [
48
+ DescendantsContextProvider,
49
+ useDescendantsContext,
50
+ ] = createContext<UseDescendantsReturn>({
51
+ name: "DescendantsProvider",
52
+ errorMessage: "useDescendantsContext must be used within DescendantsProvider",
53
+ })
54
+
44
55
  /**
45
56
  * @internal
57
+ * This hook provides information a descendant such as:
46
- *
58
+ * - Its index compared to other descendants
59
+ * - ref callback to register the descendant
47
- * Class to manage descendants and their relative indices in the DOM.
60
+ * - Its enabled index compared to other enabled descendants
48
- * It uses `node.compareDocumentPosition(...)` under the hood
49
61
  */
62
+ function useDescendant<T extends HTMLElement = HTMLElement, K = {}>(
50
- export class DescendantsManager<
63
+ options?: DescendantOptions<K>,
51
- T extends HTMLElement,
52
- K extends Record<string, any> = {}
53
- > {
64
+ ) {
54
- private descendants = new Map<T, Descendant<T, K>>()
65
+ const descendants = useDescendantsContext()
66
+ const [index, setIndex] = useState(-1)
67
+ const ref = useRef<T>(null)
55
68
 
56
- register = (nodeOrOptions: T | null | DescendantOptions<K>) => {
69
+ useSafeLayoutEffect(() => {
70
+ return () => {
57
- if (nodeOrOptions == null) return
71
+ if (!ref.current) return
58
-
59
- if (isElement(nodeOrOptions)) {
60
- return this.registerNode(nodeOrOptions)
72
+ descendants.unregister(ref.current)
61
73
  }
74
+ }, [])
62
75
 
63
- return (node: T | null) => {
76
+ useSafeLayoutEffect(() => {
77
+ if (!ref.current) return
64
- this.registerNode(node, nodeOrOptions)
78
+ const dataIndex = Number(ref.current.dataset.index)
79
+ if (index != dataIndex && !Number.isNaN(dataIndex)) {
80
+ setIndex(dataIndex)
65
81
  }
66
- }
82
+ })
67
83
 
68
- unregister = (node: T) => {
84
+ const refCallback = options
69
- this.descendants.delete(node)
85
+ ? cast<RefCallback<T>>(descendants.register(options))
70
- const sorted = sortNodes(Array.from(this.descendants.keys()))
86
+ : cast<RefCallback<T>>(descendants.register)
71
- this.assignIndex(sorted)
72
- }
73
87
 
74
- destroy = () => {
88
+ return {
75
- this.descendants.clear()
89
+ descendants,
90
+ index,
91
+ enabledIndex: descendants.enabledIndexOf(ref.current),
92
+ register: mergeRefs(refCallback, ref),
76
93
  }
94
+ }
77
95
 
78
- private assignIndex = (descendants: Node[]) => {
96
+ /* -------------------------------------------------------------------------------------------------
79
- this.descendants.forEach((descendant) => {
80
- const index = descendants.indexOf(descendant.node)
97
+ * Function that provides strongly typed versions of the context provider and hooks above.
81
- descendant.index = index
98
+ To be used in component-land
82
- descendant.node.dataset.index = descendant.index.toString()
99
+ * -----------------------------------------------------------------------------------------------*/
83
- })
84
- }
85
100
 
86
- count = () => this.descendants.size
101
+ export function createDescendantContext<
102
+ T extends HTMLElement = HTMLElement,
103
+ K = {}
104
+ >() {
105
+ type ContextProviderType = React.Provider<DescendantsManager<T, K>>
106
+ const ContextProvider = cast<ContextProviderType>(DescendantsContextProvider)
87
107
 
108
+ const _useDescendantsContext = () =>
88
- enabledCount = () => this.enabledValues().length
109
+ cast<DescendantsManager<T, K>>(useDescendantsContext())
89
110
 
90
- values = () => {
91
- const values = Array.from(this.descendants.values())
111
+ const _useDescendant = (options?: DescendantOptions<K>) =>
92
- return values.sort((a, b) => a.index - b.index)
112
+ useDescendant<T, K>(options)
93
- }
94
113
 
95
- enabledValues = () => {
96
- return this.values().filter((descendant) => !descendant.disabled)
114
+ const _useDescendants = () => useDescendants<T, K>()
97
- }
98
115
 
116
+ return [
99
- item = (index: number) => {
117
+ // context provider
118
+ ContextProvider,
100
- if (this.count() === 0) return undefined
119
+ // call this when you need to read from context
101
- return this.values()[index]
120
+ _useDescendantsContext,
121
+ // descendants state information, to be called and passed to `ContextProvider`
122
+ _useDescendants,
123
+ // descendant index information
124
+ _useDescendant,
125
+ ] as const
102
- }
126
+ }
103
127
 
104
- enabledItem = (index: number) => {
105
- if (this.enabledCount() === 0) return undefined
106
- return this.enabledValues()[index]
107
- }
108
128
 
109
- first = () => this.item(0)
110
-
111
- firstEnabled = () => this.enabledItem(0)
112
-
113
- last = () => this.item(this.descendants.size - 1)
114
-
115
- lastEnabled = () => {
116
- const lastIndex = this.enabledValues().length - 1
117
- return this.enabledItem(lastIndex)
118
- }
119
-
120
- indexOf = (node: T | null) => {
121
- if (!node) return -1
122
- return this.descendants.get(node)?.index ?? -1
123
- }
124
-
125
- enabledIndexOf = (node: T | null) => {
126
- if (node == null) return -1
127
- return this.enabledValues().findIndex((i) => i.node.isSameNode(node))
128
- }
129
-
130
- next = (index: number, loop = true) => {
131
- const next = getNextIndex(index, this.count(), loop)
132
- return this.item(next)
133
- }
134
-
135
- nextEnabled = (index: number, loop = true) => {
136
- const item = this.item(index)
137
- if (!item) return
138
- const enabledIndex = this.enabledIndexOf(item.node)
139
- const nextEnabledIndex = getNextIndex(
140
- enabledIndex,
141
- this.enabledCount(),
142
- loop,
143
- )
144
- return this.enabledItem(nextEnabledIndex)
145
- }
146
-
147
- prev = (index: number, loop = true) => {
148
- const prev = getPrevIndex(index, this.count() - 1, loop)
149
- return this.item(prev)
150
- }
151
-
152
- prevEnabled = (index: number, loop = true) => {
153
- const item = this.item(index)
154
- if (!item) return
155
- const enabledIndex = this.enabledIndexOf(item.node)
156
- const prevEnabledIndex = getPrevIndex(
157
- enabledIndex,
158
- this.enabledCount() - 1,
159
- loop,
160
- )
161
- return this.enabledItem(prevEnabledIndex)
162
- }
163
-
164
- private registerNode = (node: T | null, options?: DescendantOptions<K>) => {
165
- if (!node || this.descendants.has(node)) return
166
-
167
- const keys = Array.from(this.descendants.keys()).concat(node)
168
- const sorted = sortNodes(keys)
169
-
170
- if (options?.disabled) {
171
- options.disabled = !!options.disabled
172
- }
173
-
174
- const descendant = { node, index: -1, ...options }
175
-
176
- this.descendants.set(node, descendant as Descendant<T, K>)
177
-
178
- this.assignIndex(sorted)
179
- }
180
- }
181
-
182
129
  ```

1

`unregister`の記載場所と詳細

2021/12/15 00:23

投稿

hayatoganbaru
hayatoganbaru

スコア7

title CHANGED
File without changes
body CHANGED
@@ -5,4 +5,178 @@
5
5
  直訳してみて、プロパティの'unregister'はタイプ'UseDescendantsReturn'に存在しませんという意味だと捉えました。
6
6
  「TS2339 エラー」でググってみましたが、エラーを解消するヒントは得られませんでした。
7
7
  'unregister'なんて記述はした覚えが全くないので困っています。
8
- 解決へのアプローチをお教え頂けないでしょうか?
8
+ 解決へのアプローチをお教え頂けないでしょうか?
9
+
10
+ # `unregister`の記載場所と詳細
11
+
12
+ node_modules/@chakra-ui/descendant/src/use-descendant.tsに記述があるとのことです。
13
+
14
+ 実際にそのファイルは以下のようになっております。
15
+ エラー箇所のような記述を私は見つけることができませんでした。
16
+
17
+
18
+ ```
19
+ import { sortNodes, isElement, getNextIndex, getPrevIndex } from "./utils"
20
+
21
+ export type DescendantOptions<T = {}> = T & {
22
+ /**
23
+ * If `true`, the item will be registered in all nodes map
24
+ * but omitted from enabled nodes map
25
+ */
26
+ disabled?: boolean
27
+ /**
28
+ * The id of the item
29
+ */
30
+ id?: string
31
+ }
32
+
33
+ export type Descendant<T, K> = DescendantOptions<K> & {
34
+ /**
35
+ * DOM element of the item
36
+ */
37
+ node: T
38
+ /**
39
+ * index of item in all nodes map and enabled nodes map
40
+ */
41
+ index: number
42
+ }
43
+
44
+ /**
45
+ * @internal
46
+ *
47
+ * Class to manage descendants and their relative indices in the DOM.
48
+ * It uses `node.compareDocumentPosition(...)` under the hood
49
+ */
50
+ export class DescendantsManager<
51
+ T extends HTMLElement,
52
+ K extends Record<string, any> = {}
53
+ > {
54
+ private descendants = new Map<T, Descendant<T, K>>()
55
+
56
+ register = (nodeOrOptions: T | null | DescendantOptions<K>) => {
57
+ if (nodeOrOptions == null) return
58
+
59
+ if (isElement(nodeOrOptions)) {
60
+ return this.registerNode(nodeOrOptions)
61
+ }
62
+
63
+ return (node: T | null) => {
64
+ this.registerNode(node, nodeOrOptions)
65
+ }
66
+ }
67
+
68
+ unregister = (node: T) => {
69
+ this.descendants.delete(node)
70
+ const sorted = sortNodes(Array.from(this.descendants.keys()))
71
+ this.assignIndex(sorted)
72
+ }
73
+
74
+ destroy = () => {
75
+ this.descendants.clear()
76
+ }
77
+
78
+ private assignIndex = (descendants: Node[]) => {
79
+ this.descendants.forEach((descendant) => {
80
+ const index = descendants.indexOf(descendant.node)
81
+ descendant.index = index
82
+ descendant.node.dataset.index = descendant.index.toString()
83
+ })
84
+ }
85
+
86
+ count = () => this.descendants.size
87
+
88
+ enabledCount = () => this.enabledValues().length
89
+
90
+ values = () => {
91
+ const values = Array.from(this.descendants.values())
92
+ return values.sort((a, b) => a.index - b.index)
93
+ }
94
+
95
+ enabledValues = () => {
96
+ return this.values().filter((descendant) => !descendant.disabled)
97
+ }
98
+
99
+ item = (index: number) => {
100
+ if (this.count() === 0) return undefined
101
+ return this.values()[index]
102
+ }
103
+
104
+ enabledItem = (index: number) => {
105
+ if (this.enabledCount() === 0) return undefined
106
+ return this.enabledValues()[index]
107
+ }
108
+
109
+ first = () => this.item(0)
110
+
111
+ firstEnabled = () => this.enabledItem(0)
112
+
113
+ last = () => this.item(this.descendants.size - 1)
114
+
115
+ lastEnabled = () => {
116
+ const lastIndex = this.enabledValues().length - 1
117
+ return this.enabledItem(lastIndex)
118
+ }
119
+
120
+ indexOf = (node: T | null) => {
121
+ if (!node) return -1
122
+ return this.descendants.get(node)?.index ?? -1
123
+ }
124
+
125
+ enabledIndexOf = (node: T | null) => {
126
+ if (node == null) return -1
127
+ return this.enabledValues().findIndex((i) => i.node.isSameNode(node))
128
+ }
129
+
130
+ next = (index: number, loop = true) => {
131
+ const next = getNextIndex(index, this.count(), loop)
132
+ return this.item(next)
133
+ }
134
+
135
+ nextEnabled = (index: number, loop = true) => {
136
+ const item = this.item(index)
137
+ if (!item) return
138
+ const enabledIndex = this.enabledIndexOf(item.node)
139
+ const nextEnabledIndex = getNextIndex(
140
+ enabledIndex,
141
+ this.enabledCount(),
142
+ loop,
143
+ )
144
+ return this.enabledItem(nextEnabledIndex)
145
+ }
146
+
147
+ prev = (index: number, loop = true) => {
148
+ const prev = getPrevIndex(index, this.count() - 1, loop)
149
+ return this.item(prev)
150
+ }
151
+
152
+ prevEnabled = (index: number, loop = true) => {
153
+ const item = this.item(index)
154
+ if (!item) return
155
+ const enabledIndex = this.enabledIndexOf(item.node)
156
+ const prevEnabledIndex = getPrevIndex(
157
+ enabledIndex,
158
+ this.enabledCount() - 1,
159
+ loop,
160
+ )
161
+ return this.enabledItem(prevEnabledIndex)
162
+ }
163
+
164
+ private registerNode = (node: T | null, options?: DescendantOptions<K>) => {
165
+ if (!node || this.descendants.has(node)) return
166
+
167
+ const keys = Array.from(this.descendants.keys()).concat(node)
168
+ const sorted = sortNodes(keys)
169
+
170
+ if (options?.disabled) {
171
+ options.disabled = !!options.disabled
172
+ }
173
+
174
+ const descendant = { node, index: -1, ...options }
175
+
176
+ this.descendants.set(node, descendant as Descendant<T, K>)
177
+
178
+ this.assignIndex(sorted)
179
+ }
180
+ }
181
+
182
+ ```