1 | import { isObservableArray, isObservableObject, isObservableMap, untracked } from "mobx"
|
2 |
|
3 |
|
4 | function createChainableTypeChecker(validator: React.Validator<any>): React.Requireable<any> {
|
5 | function checkType(
|
6 | isRequired: boolean,
|
7 | props: any,
|
8 | propName: string,
|
9 | componentName: string,
|
10 | location: string,
|
11 | propFullName: string,
|
12 | ...rest: any[]
|
13 | ) {
|
14 | return untracked(() => {
|
15 | componentName = componentName || "<<anonymous>>"
|
16 | propFullName = propFullName || propName
|
17 | if (props[propName] == null) {
|
18 | if (isRequired) {
|
19 | const actual = props[propName] === null ? "null" : "undefined"
|
20 | return new Error(
|
21 | "The " +
|
22 | location +
|
23 | " `" +
|
24 | propFullName +
|
25 | "` is marked as required " +
|
26 | "in `" +
|
27 | componentName +
|
28 | "`, but its value is `" +
|
29 | actual +
|
30 | "`."
|
31 | )
|
32 | }
|
33 | return null
|
34 | } else {
|
35 |
|
36 | return validator(props, propName, componentName, location, propFullName, ...rest)
|
37 | }
|
38 | })
|
39 | }
|
40 |
|
41 | const chainedCheckType: any = checkType.bind(null, false)
|
42 |
|
43 | chainedCheckType.isRequired = checkType.bind(null, true)
|
44 | return chainedCheckType
|
45 | }
|
46 |
|
47 |
|
48 | function isSymbol(propType: any, propValue: any): boolean {
|
49 |
|
50 | if (propType === "symbol") {
|
51 | return true
|
52 | }
|
53 |
|
54 |
|
55 | if (propValue["@@toStringTag"] === "Symbol") {
|
56 | return true
|
57 | }
|
58 |
|
59 |
|
60 | if (typeof Symbol === "function" && propValue instanceof Symbol) {
|
61 | return true
|
62 | }
|
63 |
|
64 | return false
|
65 | }
|
66 |
|
67 |
|
68 | function getPropType(propValue: any): string {
|
69 | const propType = typeof propValue
|
70 | if (Array.isArray(propValue)) {
|
71 | return "array"
|
72 | }
|
73 | if (propValue instanceof RegExp) {
|
74 |
|
75 |
|
76 |
|
77 | return "object"
|
78 | }
|
79 | if (isSymbol(propType, propValue)) {
|
80 | return "symbol"
|
81 | }
|
82 | return propType
|
83 | }
|
84 |
|
85 |
|
86 |
|
87 | function getPreciseType(propValue: any): string {
|
88 | const propType = getPropType(propValue)
|
89 | if (propType === "object") {
|
90 | if (propValue instanceof Date) {
|
91 | return "date"
|
92 | } else if (propValue instanceof RegExp) {
|
93 | return "regexp"
|
94 | }
|
95 | }
|
96 | return propType
|
97 | }
|
98 |
|
99 | function createObservableTypeCheckerCreator(
|
100 | allowNativeType: any,
|
101 | mobxType: any
|
102 | ): React.Requireable<any> {
|
103 | return createChainableTypeChecker((props, propName, componentName, location, propFullName) => {
|
104 | return untracked(() => {
|
105 | if (allowNativeType) {
|
106 | if (getPropType(props[propName]) === mobxType.toLowerCase()) return null
|
107 | }
|
108 | let mobxChecker
|
109 | switch (mobxType) {
|
110 | case "Array":
|
111 | mobxChecker = isObservableArray
|
112 | break
|
113 | case "Object":
|
114 | mobxChecker = isObservableObject
|
115 | break
|
116 | case "Map":
|
117 | mobxChecker = isObservableMap
|
118 | break
|
119 | default:
|
120 | throw new Error(`Unexpected mobxType: ${mobxType}`)
|
121 | }
|
122 | const propValue = props[propName]
|
123 | if (!mobxChecker(propValue)) {
|
124 | const preciseType = getPreciseType(propValue)
|
125 | const nativeTypeExpectationMessage = allowNativeType
|
126 | ? " or javascript `" + mobxType.toLowerCase() + "`"
|
127 | : ""
|
128 | return new Error(
|
129 | "Invalid prop `" +
|
130 | propFullName +
|
131 | "` of type `" +
|
132 | preciseType +
|
133 | "` supplied to" +
|
134 | " `" +
|
135 | componentName +
|
136 | "`, expected `mobx.Observable" +
|
137 | mobxType +
|
138 | "`" +
|
139 | nativeTypeExpectationMessage +
|
140 | "."
|
141 | )
|
142 | }
|
143 | return null
|
144 | })
|
145 | })
|
146 | }
|
147 |
|
148 | function createObservableArrayOfTypeChecker(
|
149 | allowNativeType: boolean,
|
150 | typeChecker: React.Validator<any>
|
151 | ) {
|
152 | return createChainableTypeChecker(
|
153 | (props, propName, componentName, location, propFullName, ...rest) => {
|
154 | return untracked(() => {
|
155 | if (typeof typeChecker !== "function") {
|
156 | return new Error(
|
157 | "Property `" +
|
158 | propFullName +
|
159 | "` of component `" +
|
160 | componentName +
|
161 | "` has " +
|
162 | "invalid PropType notation."
|
163 | )
|
164 | } else {
|
165 | let error = createObservableTypeCheckerCreator(allowNativeType, "Array")(
|
166 | props,
|
167 | propName,
|
168 | componentName,
|
169 | location,
|
170 | propFullName
|
171 | )
|
172 |
|
173 | if (error instanceof Error) return error
|
174 | const propValue = props[propName]
|
175 | for (let i = 0; i < propValue.length; i++) {
|
176 | error = (typeChecker as React.Validator<any>)(
|
177 | propValue,
|
178 | i as any,
|
179 | componentName,
|
180 | location,
|
181 | propFullName + "[" + i + "]",
|
182 | ...rest
|
183 | )
|
184 | if (error instanceof Error) return error
|
185 | }
|
186 |
|
187 | return null
|
188 | }
|
189 | })
|
190 | }
|
191 | )
|
192 | }
|
193 |
|
194 | const observableArray = createObservableTypeCheckerCreator(false, "Array")
|
195 | const observableArrayOf = createObservableArrayOfTypeChecker.bind(null, false)
|
196 | const observableMap = createObservableTypeCheckerCreator(false, "Map")
|
197 | const observableObject = createObservableTypeCheckerCreator(false, "Object")
|
198 | const arrayOrObservableArray = createObservableTypeCheckerCreator(true, "Array")
|
199 | const arrayOrObservableArrayOf = createObservableArrayOfTypeChecker.bind(null, true)
|
200 | const objectOrObservableObject = createObservableTypeCheckerCreator(true, "Object")
|
201 |
|
202 | export const PropTypes = {
|
203 | observableArray,
|
204 | observableArrayOf,
|
205 | observableMap,
|
206 | observableObject,
|
207 | arrayOrObservableArray,
|
208 | arrayOrObservableArrayOf,
|
209 | objectOrObservableObject
|
210 | }
|