@es-joy/jsoe 0.1.0 → 0.3.0
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/.eslintrc.cjs +2 -1
- package/CHANGES.md +13 -0
- package/README.md +4 -4
- package/demo/index.html +1 -0
- package/demo/index.js +147 -14
- package/index.html +10 -0
- package/package.json +9 -9
- package/src/formatAndTypeChoices.js +245 -0
- package/src/formats/indexedDBKey.js +5 -4
- package/src/formats/json.js +1 -1
- package/src/formats/schemaAndArbitrary.js +1 -1
- package/src/formats/schemaOnly.js +1 -1
- package/src/formats/structuredCloning.js +4 -22
- package/src/formats.js +17 -206
- package/src/fundamentalTypes/BooleanObjectType.js +1 -1
- package/src/fundamentalTypes/NumberObjectType.js +1 -1
- package/src/fundamentalTypes/StringObjectType.js +1 -1
- package/src/fundamentalTypes/arrayReferenceType.js +1 -1
- package/src/fundamentalTypes/arrayType.js +3 -3
- package/src/fundamentalTypes/bigintType.js +1 -1
- package/src/fundamentalTypes/dateType.js +1 -1
- package/src/fundamentalTypes/nullType.js +1 -1
- package/src/fundamentalTypes/numberType.js +1 -1
- package/src/fundamentalTypes/objectReferenceType.js +1 -1
- package/src/fundamentalTypes/objectType.js +1 -1
- package/src/fundamentalTypes/regexpType.js +1 -1
- package/src/fundamentalTypes/sparseUndefinedType.js +1 -1
- package/src/fundamentalTypes/stringType.js +1 -1
- package/src/fundamentalTypes/undefinedType.js +1 -1
- package/src/index.js +7 -2
- package/src/jsoe.css +58 -0
- package/src/subTypes/falseType.js +1 -1
- package/src/subTypes/trueType.js +1 -1
- package/src/superTypes/SpecialNumberSuperType.js +17 -10
- package/src/superTypes/SpecialRealNumberSuperType.js +56 -0
- package/src/typeChoices.js +229 -117
- package/src/types.js +56 -37
- package/vendor/typeson-registry/dist/index.js +19 -3
- package/src/superTypes/InfinitiesSuperType.js +0 -49
package/src/formats.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import Types from './types.js';
|
|
2
|
-
import {jml} from '../vendor/jamilih/dist/jml-es.js';
|
|
3
|
-
|
|
4
|
-
import {$e, DOM} from './utils/templateUtils.js';
|
|
5
|
-
import dialogs from './utils/dialogs.js';
|
|
6
|
-
|
|
7
1
|
import * as indexedDBKey from './formats/indexedDBKey.js';
|
|
8
2
|
import * as json from './formats/json.js';
|
|
9
3
|
import * as structuredCloning from './formats/structuredCloning.js';
|
|
10
4
|
|
|
5
|
+
/* schema:
|
|
6
|
+
const getTypeForFormatStateAndValue = ({format, state, value}) => {
|
|
7
|
+
const valType = new Typeson().register(
|
|
8
|
+
structuredCloningThrowing
|
|
9
|
+
).rootTypeName(value);
|
|
10
|
+
return canonicalToAvailableType(format, state, valType, value);
|
|
11
|
+
};
|
|
12
|
+
Formats.getTypeForFormatStateAndValue = getTypeForFormatStateAndValue;
|
|
13
|
+
*/
|
|
14
|
+
|
|
11
15
|
// Using methods ensure we have fresh copies
|
|
12
16
|
const Formats = {
|
|
13
17
|
availableFormats: {
|
|
@@ -23,207 +27,14 @@ const Formats = {
|
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
|
-
*
|
|
27
|
-
* @typedef {any} StructuredCloneValue
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @callback BuildTypeChoices
|
|
32
|
-
* @param {object} cfg
|
|
33
|
-
* @param {string} cfg.format
|
|
34
|
-
* @param {string} cfg.typeNamespace
|
|
35
|
-
* @param {StructuredCloneValue} cfg.value
|
|
36
|
-
* @param {boolean} [cfg.setValue=false]
|
|
37
|
-
* @param {string} cfg.state
|
|
38
|
-
* @param {string} cfg.keySelectClass
|
|
39
|
-
* @param {boolean} cfg.requireObject
|
|
40
|
-
* @param {boolean} cfg.objectHasValue
|
|
41
|
-
* @param {RootElement} cfg.topRoot Always a `div` element?
|
|
42
|
-
* @param {string} cfg.schema Schema name
|
|
43
|
-
* @param {string} cfg.schemaContent Schema contents
|
|
44
|
-
* @returns {JamilihArray}
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @type {BuildTypeChoices}
|
|
30
|
+
* @type {import('./formats/structuredCloning.js').FormatIterator}
|
|
49
31
|
*/
|
|
50
|
-
|
|
51
|
-
format
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
state,
|
|
56
|
-
// itemIndex = 0,
|
|
57
|
-
keySelectClass,
|
|
58
|
-
requireObject,
|
|
59
|
-
objectHasValue,
|
|
60
|
-
topRoot,
|
|
61
|
-
schema,
|
|
62
|
-
schemaContent
|
|
63
|
-
}) => {
|
|
64
|
-
// console.log('format', format, 'state', state, 'path', typeNamespace);
|
|
65
|
-
const typeOptions = requireObject
|
|
66
|
-
? [Types.getOptionForType('object')]
|
|
67
|
-
: Types.getTypeOptionsForFormatAndState(format, state);
|
|
68
|
-
|
|
69
|
-
let editUI;
|
|
70
|
-
const sel = jml('select', {
|
|
71
|
-
hidden: requireObject,
|
|
72
|
-
class: `typeChoices-${typeNamespace}${keySelectClass
|
|
73
|
-
? ' ' + keySelectClass
|
|
74
|
-
: ''
|
|
75
|
-
}`,
|
|
76
|
-
// is: 'type-choices',
|
|
77
|
-
$custom: {
|
|
78
|
-
$setType ({type, baseValue, bringIntoFocus}) {
|
|
79
|
-
this.value = type;
|
|
80
|
-
this.$setStyles();
|
|
81
|
-
this.$addAndValidateEditUI({baseValue, bringIntoFocus});
|
|
82
|
-
},
|
|
83
|
-
$setTypeNoEditUI ({type}) {
|
|
84
|
-
this.value = type;
|
|
85
|
-
this.$setStyles();
|
|
86
|
-
},
|
|
87
|
-
$setStyles () {
|
|
88
|
-
const {value: type} = this;
|
|
89
|
-
this.dataset.type = type; // Used for styling
|
|
90
|
-
const parEl = this.parentElement;
|
|
91
|
-
if (parEl.nodeName.toLowerCase() === 'fieldset') {
|
|
92
|
-
parEl.dataset.type = type;
|
|
93
|
-
DOM.filterChildElements(parEl, 'legend').forEach((legend) => {
|
|
94
|
-
legend.dataset.type = type;
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
$getTypeRoot () {
|
|
99
|
-
const container = this.$getContainer();
|
|
100
|
-
/* istanbul ignore if -- How to replicate? */
|
|
101
|
-
if (!container) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
return $e(container, 'div[data-type]');
|
|
105
|
-
},
|
|
106
|
-
$addAndValidateEditUI ({baseValue, bringIntoFocus} = {}) {
|
|
107
|
-
const {value: type} = this;
|
|
108
|
-
|
|
109
|
-
const container = this.$getContainer();
|
|
110
|
-
DOM.removeChildren(container);
|
|
111
|
-
|
|
112
|
-
if (!type) { return; }
|
|
113
|
-
let topRoot = this.$getTopRoot();
|
|
114
|
-
|
|
115
|
-
// Todo (low): Try to avoid need for `baseValue`
|
|
116
|
-
// (needed by arrayNonindexKeys for setting an array
|
|
117
|
-
// length and avoiding errors); could set all
|
|
118
|
-
// values through here?
|
|
119
|
-
editUI = Types.getUIForModeAndType({
|
|
120
|
-
readonly: false,
|
|
121
|
-
typeNamespace,
|
|
122
|
-
type,
|
|
123
|
-
bringIntoFocus,
|
|
124
|
-
hasValue: type === 'arrayNonindexKeys' && baseValue,
|
|
125
|
-
value: baseValue,
|
|
126
|
-
buildTypeChoices,
|
|
127
|
-
format,
|
|
128
|
-
topRoot
|
|
129
|
-
});
|
|
130
|
-
this.$addEditUI({editUI});
|
|
131
|
-
this.$validate();
|
|
132
|
-
topRoot = this.$getTopRoot(); // May be existing now
|
|
133
|
-
// Needed; Array/object ref somewhere could now be valid or invalid
|
|
134
|
-
Types.validateAllReferences({topRoot});
|
|
135
|
-
},
|
|
136
|
-
$addTypeAndEditUI ({type, editUI}) {
|
|
137
|
-
this.$setTypeNoEditUI({type});
|
|
138
|
-
this.$addEditUI({editUI});
|
|
139
|
-
},
|
|
140
|
-
$addEditUI ({editUI}) {
|
|
141
|
-
const container = this.$getContainer();
|
|
142
|
-
jml(editUI, container);
|
|
143
|
-
},
|
|
144
|
-
$getContainer () {
|
|
145
|
-
return this.nextElementSibling;
|
|
146
|
-
},
|
|
147
|
-
$getTopRoot () {
|
|
148
|
-
return topRoot || this.$getTypeRoot();
|
|
149
|
-
},
|
|
150
|
-
$validate () {
|
|
151
|
-
const {value: type} = this;
|
|
152
|
-
const container = this.$getContainer();
|
|
153
|
-
if (!container.firstElementChild) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
const editUI = container.firstElementChild;
|
|
157
|
-
return Types.validate({
|
|
158
|
-
type, root: editUI, topRoot: this.$getTopRoot()
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
$on: {change (e) {
|
|
163
|
-
// We don't want form `onchange` to run `$checkForKeyDuplicates`
|
|
164
|
-
// again (through `addAndValidateEditUI`->`validateAllReferences`)
|
|
165
|
-
e.stopPropagation();
|
|
166
|
-
this.$addAndValidateEditUI();
|
|
167
|
-
this.$setStyles();
|
|
168
|
-
}}
|
|
169
|
-
}, [
|
|
170
|
-
['option', {value: ''}, [
|
|
171
|
-
'(Choose a type)'
|
|
172
|
-
]],
|
|
173
|
-
...typeOptions.map(
|
|
174
|
-
([optText, optAtts]) => [
|
|
175
|
-
'option',
|
|
176
|
-
optAtts ||
|
|
177
|
-
/* istanbul ignore next -- Should always have atts */
|
|
178
|
-
{},
|
|
179
|
-
[optText]
|
|
180
|
-
]
|
|
181
|
-
)
|
|
182
|
-
]);
|
|
183
|
-
if (setValue || (requireObject && !objectHasValue)) {
|
|
184
|
-
setTimeout(async () => {
|
|
185
|
-
if (!setValue) { // if (requireObject && !objectHasValue) {
|
|
186
|
-
// Todo (low): We could auto-populate keypath if has
|
|
187
|
-
// keypath (and we probably also only want if
|
|
188
|
-
// not autoincrement)
|
|
189
|
-
value = {};
|
|
190
|
-
}
|
|
191
|
-
try {
|
|
192
|
-
const rootEditUI = await Formats.availableFormats[format].iterate(
|
|
193
|
-
value,
|
|
194
|
-
{
|
|
195
|
-
readonly: false,
|
|
196
|
-
typeNamespace,
|
|
197
|
-
schema,
|
|
198
|
-
schemaContent
|
|
199
|
-
}
|
|
200
|
-
);
|
|
201
|
-
const type = Types.getTypeForRoot(rootEditUI);
|
|
202
|
-
sel.$addTypeAndEditUI({type, editUI: rootEditUI});
|
|
203
|
-
} catch (err) {
|
|
204
|
-
/* istanbul ignore next -- At least some errors handled earlier */
|
|
205
|
-
dialogs.alert({
|
|
206
|
-
message: 'The object to be added had types not supported ' +
|
|
207
|
-
'by the current format.'
|
|
208
|
-
});
|
|
209
|
-
/* istanbul ignore next -- How to trigger? */
|
|
210
|
-
console.log('err', err);
|
|
211
|
-
}
|
|
32
|
+
export async function getControlsForFormatAndValue (format, record, stateObj) {
|
|
33
|
+
return await Formats.availableFormats[format]
|
|
34
|
+
.iterate(record, {
|
|
35
|
+
format,
|
|
36
|
+
...stateObj
|
|
212
37
|
});
|
|
213
|
-
|
|
214
|
-
return [
|
|
215
|
-
sel,
|
|
216
|
-
['div', {class: 'typeContainer'}]
|
|
217
|
-
];
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
/* schema:
|
|
221
|
-
Formats.getTypeForFormatStateAndValue = ({format, state, value}) => {
|
|
222
|
-
const valType = new Typeson().register(
|
|
223
|
-
structuredCloningThrowing
|
|
224
|
-
).rootTypeName(value);
|
|
225
|
-
return canonicalToAvailableType(format, state, valType, value);
|
|
226
|
-
};
|
|
227
|
-
*/
|
|
38
|
+
}
|
|
228
39
|
|
|
229
40
|
export default Formats;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '../utils/jsonPointer.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @type {TypeObject}
|
|
11
|
+
* @type {import('../types.js').TypeObject}
|
|
12
12
|
*/
|
|
13
13
|
const arrayType = {
|
|
14
14
|
option: ['Array'],
|
|
@@ -646,14 +646,14 @@ const arrayType = {
|
|
|
646
646
|
], arrayItems);
|
|
647
647
|
// We must ensure fieldset is built before passing it
|
|
648
648
|
jml({'#': [
|
|
649
|
-
...buildTypeChoices({
|
|
649
|
+
...(buildTypeChoices({
|
|
650
650
|
resultType,
|
|
651
651
|
topRoot,
|
|
652
652
|
format,
|
|
653
653
|
state: type,
|
|
654
654
|
itemIndex,
|
|
655
655
|
typeNamespace
|
|
656
|
-
}),
|
|
656
|
+
}).domArray),
|
|
657
657
|
nbsp.repeat(2),
|
|
658
658
|
['button', {$on: {click (e) {
|
|
659
659
|
e.preventDefault();
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export {default as Types} from './types.js';
|
|
2
2
|
|
|
3
|
-
export {default as Formats} from './formats.js';
|
|
3
|
+
export {default as Formats, getControlsForFormatAndValue} from './formats.js';
|
|
4
4
|
|
|
5
5
|
export {
|
|
6
|
-
|
|
6
|
+
buildTypeChoices as typeChoices
|
|
7
7
|
} from './typeChoices.js';
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
formatAndTypeChoices,
|
|
11
|
+
getFormatAndSchemaChoices
|
|
12
|
+
} from './formatAndTypeChoices.js';
|
package/src/jsoe.css
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
input:invalid {
|
|
2
|
+
background-color: pink;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
[data-type="undef"] {
|
|
6
|
+
background-color: green;
|
|
7
|
+
}
|
|
8
|
+
[data-type="null"] {
|
|
9
|
+
background-color: lightgreen;
|
|
10
|
+
}
|
|
11
|
+
[data-type="true"] {
|
|
12
|
+
background-color: blue;
|
|
13
|
+
color: white;
|
|
14
|
+
}
|
|
15
|
+
[data-type="false"] {
|
|
16
|
+
background-color: pink;
|
|
17
|
+
}
|
|
18
|
+
[data-type="number"] {
|
|
19
|
+
background-color: violet;
|
|
20
|
+
}
|
|
21
|
+
[data-type="string"] {
|
|
22
|
+
background-color: lightblue;
|
|
23
|
+
}
|
|
24
|
+
[data-type="array"], [data-type="sparseArrays"], [data-type="arrayNonindexKeys"] {
|
|
25
|
+
background-color: lightyellow;
|
|
26
|
+
}
|
|
27
|
+
[data-type="object"] {
|
|
28
|
+
background-color: orange;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
[data-type="arrayReference"] {
|
|
33
|
+
color: white;
|
|
34
|
+
background-color: indigo;
|
|
35
|
+
}
|
|
36
|
+
[data-type="objectReference"] {
|
|
37
|
+
color: white;
|
|
38
|
+
background-color: purple;
|
|
39
|
+
}
|
|
40
|
+
[data-type="date"] {
|
|
41
|
+
background-color: teal;
|
|
42
|
+
}
|
|
43
|
+
[data-type="regexp"] {
|
|
44
|
+
background-color: yellow;
|
|
45
|
+
}
|
|
46
|
+
[data-type="SpecialNumber"], [data-type="SpecialRealNumber"] {
|
|
47
|
+
background-color: red;
|
|
48
|
+
}
|
|
49
|
+
[data-type="BooleanObject"], [data-type="NumberObject"], [data-type="StringObject"] {
|
|
50
|
+
background-color: aqua;
|
|
51
|
+
}
|
|
52
|
+
[data-type="blobHTML"] {
|
|
53
|
+
background-color: sandybrown;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[class^="propertyName-"] {
|
|
57
|
+
background-color: yellow;
|
|
58
|
+
}
|
package/src/subTypes/trueType.js
CHANGED
|
@@ -9,16 +9,18 @@ import {$e} from '../utils/templateUtils.js';
|
|
|
9
9
|
* @type {SuperTypeObject}
|
|
10
10
|
*/
|
|
11
11
|
const SpecialNumberSuperType = {
|
|
12
|
-
option: ['
|
|
13
|
-
childTypes: ['infinity', 'negativeInfinity', 'nan'],
|
|
14
|
-
stringRegex: /^(?:NaN|-?Infinity)$/u,
|
|
12
|
+
option: ['Special Number', {title: '`NaN`, `Infinity`, `-Infinity`, `-0`'}],
|
|
13
|
+
childTypes: ['infinity', 'negativeInfinity', 'nan', 'negativeZero'],
|
|
14
|
+
stringRegex: /^(?:NaN|-?Infinity|-0)$/u,
|
|
15
15
|
toValue (s) {
|
|
16
16
|
return {
|
|
17
|
-
value: s === '
|
|
18
|
-
?
|
|
19
|
-
: s === '
|
|
20
|
-
? Number.
|
|
21
|
-
:
|
|
17
|
+
value: s === '-0'
|
|
18
|
+
? -0
|
|
19
|
+
: s === 'NaN'
|
|
20
|
+
? Number.NaN
|
|
21
|
+
: s === 'Infinity'
|
|
22
|
+
? Number.POSITIVE_INFINITY
|
|
23
|
+
: Number.NEGATIVE_INFINITY
|
|
22
24
|
};
|
|
23
25
|
},
|
|
24
26
|
getSelect ({root}) {
|
|
@@ -35,7 +37,9 @@ const SpecialNumberSuperType = {
|
|
|
35
37
|
this.getSelect({root}).value = String(value);
|
|
36
38
|
},
|
|
37
39
|
viewUI ({value}) {
|
|
38
|
-
return ['i', {dataset: {type: 'SpecialNumber'}}, [
|
|
40
|
+
return ['i', {dataset: {type: 'SpecialNumber'}}, [
|
|
41
|
+
Object.is(value, -0) ? '-0' : String(value)
|
|
42
|
+
]];
|
|
39
43
|
},
|
|
40
44
|
editUI ({typeNamespace, value = Number.NaN}) {
|
|
41
45
|
return ['div', {dataset: {type: 'SpecialNumber'}}, [
|
|
@@ -52,7 +56,10 @@ const SpecialNumberSuperType = {
|
|
|
52
56
|
}, ['Infinity']],
|
|
53
57
|
['option', {
|
|
54
58
|
value: '-Infinity', selected: value === Number.NEGATIVE_INFINITY
|
|
55
|
-
}, ['-Infinity']]
|
|
59
|
+
}, ['-Infinity']],
|
|
60
|
+
['option', {
|
|
61
|
+
value: '-0', selected: Object.is(value, -0)
|
|
62
|
+
}, ['-0']]
|
|
56
63
|
]]
|
|
57
64
|
]]
|
|
58
65
|
]];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {$e} from '../utils/templateUtils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @type {SuperTypeObject}
|
|
5
|
+
*/
|
|
6
|
+
const SpecialRealNumberSuperType = {
|
|
7
|
+
option: ['Special Real Number', {title: '`Infinity`, `-Infinity`, `-0`'}],
|
|
8
|
+
childTypes: ['infinity', 'negativeInfinity', 'negativeZero'],
|
|
9
|
+
stringRegex: /^(?:-?Infinity|-0)$/u,
|
|
10
|
+
toValue (s) {
|
|
11
|
+
return {
|
|
12
|
+
value: s === '-0'
|
|
13
|
+
? -0
|
|
14
|
+
: s === 'Infinity'
|
|
15
|
+
? Number.POSITIVE_INFINITY
|
|
16
|
+
: Number.NEGATIVE_INFINITY
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
getSelect ({root}) {
|
|
20
|
+
return $e(root, 'select');
|
|
21
|
+
},
|
|
22
|
+
getValue ({root}) {
|
|
23
|
+
return this.toValue(this.getSelect({root}).value).value;
|
|
24
|
+
},
|
|
25
|
+
setValue ({root, value}) {
|
|
26
|
+
this.getSelect({root}).value = String(value);
|
|
27
|
+
},
|
|
28
|
+
viewUI ({value}) {
|
|
29
|
+
return ['i', {dataset: {type: 'SpecialRealNumber'}}, [
|
|
30
|
+
Object.is(value, -0) ? '-0' : String(value)
|
|
31
|
+
]];
|
|
32
|
+
},
|
|
33
|
+
getInput ({root}) {
|
|
34
|
+
return $e(root, 'select');
|
|
35
|
+
},
|
|
36
|
+
editUI ({typeNamespace, value = Number.POSITIVE_INFINITY}) {
|
|
37
|
+
return ['div', {dataset: {type: 'SpecialRealNumber'}}, [
|
|
38
|
+
['label', [
|
|
39
|
+
'Special Real Number: ',
|
|
40
|
+
['select', {name: `${typeNamespace}-SpecialRealNumber`}, [
|
|
41
|
+
['option', {
|
|
42
|
+
value: 'Infinity', selected: value === Number.POSITIVE_INFINITY
|
|
43
|
+
}, ['Infinity']],
|
|
44
|
+
['option', {
|
|
45
|
+
value: '-Infinity', selected: value === Number.NEGATIVE_INFINITY
|
|
46
|
+
}, ['-Infinity']],
|
|
47
|
+
['option', {
|
|
48
|
+
value: '-0', selected: Object.is(value, -0)
|
|
49
|
+
}, ['-0']]
|
|
50
|
+
]]
|
|
51
|
+
]]
|
|
52
|
+
]];
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default SpecialRealNumberSuperType;
|