@es-joy/jsoe 0.26.1 → 0.28.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.
Files changed (158) hide show
  1. package/.gitignore +10 -0
  2. package/.npmignore +9 -0
  3. package/CHANGES.md +76 -0
  4. package/README.md +6 -4
  5. package/badges/coverage-badge.svg +1 -1
  6. package/badges/tests-badge.svg +1 -0
  7. package/dist/formats/schema.d.ts +11 -0
  8. package/dist/formats/schema.d.ts.map +1 -1
  9. package/dist/formats/structuredCloning.d.ts.map +1 -1
  10. package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
  11. package/dist/fundamentalTypes/stringType.d.ts.map +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/typeChoices.d.ts +32 -2
  15. package/dist/typeChoices.d.ts.map +1 -1
  16. package/dist/types.d.ts +8 -2
  17. package/dist/types.d.ts.map +1 -1
  18. package/dist/utils/rawTypesonEditor.d.ts +110 -0
  19. package/dist/utils/rawTypesonEditor.d.ts.map +1 -0
  20. package/dist/vendor-imports.d.ts +4 -0
  21. package/docs/proposals/raw-typeson-edit-view.md +575 -0
  22. package/eslint.config.js +36 -27
  23. package/mmr.json +6 -0
  24. package/package.json +22 -12
  25. package/pnpm-workspace.yaml +4 -2
  26. package/src/formats/schema.js +38 -7
  27. package/src/formats/structuredCloning.js +24 -17
  28. package/src/fundamentalTypes/arrayType.js +135 -7
  29. package/src/fundamentalTypes/stringType.js +5 -0
  30. package/src/jsoe.css +12 -0
  31. package/src/typeChoices.js +39 -8
  32. package/src/types.js +20 -2
  33. package/src/utils/rawTypesonEditor.js +669 -0
  34. package/src/vendor-imports.js +12 -0
  35. package/tsconfig.json +1 -1
  36. package/typings/json-6.d.ts +12 -0
  37. package/vendor/@codemirror/autocomplete/dist/index.js +2125 -0
  38. package/vendor/@codemirror/commands/dist/index.js +1826 -0
  39. package/vendor/@codemirror/lang-javascript/dist/index.js +513 -0
  40. package/vendor/@codemirror/language/dist/index.js +2693 -0
  41. package/vendor/@codemirror/lint/dist/index.js +956 -0
  42. package/vendor/@codemirror/search/dist/index.js +1238 -0
  43. package/vendor/@codemirror/state/dist/index.js +3947 -0
  44. package/vendor/@codemirror/view/dist/index.js +11867 -0
  45. package/vendor/@lezer/common/dist/index.js +2202 -0
  46. package/vendor/@lezer/highlight/dist/index.js +927 -0
  47. package/vendor/@lezer/javascript/dist/index.js +192 -0
  48. package/vendor/@lezer/lr/dist/index.js +1889 -0
  49. package/vendor/@marijn/find-cluster-break/src/index.js +87 -0
  50. package/vendor/codemirror/dist/index.js +96 -0
  51. package/vendor/crelt/index.js +28 -0
  52. package/vendor/json-6/dist/index.mjs +1783 -0
  53. package/vendor/style-mod/src/style-mod.js +172 -0
  54. package/vendor/typeson-registry/dist/index.js +83 -10
  55. package/vendor/w3c-keyname/index.js +119 -0
  56. package/vendor/zod/classic/checks.d.ts +1 -1
  57. package/vendor/zod/classic/checks.js +1 -1
  58. package/vendor/zod/classic/external.d.ts +1 -1
  59. package/vendor/zod/classic/external.js +1 -1
  60. package/vendor/zod/classic/from-json-schema.js +245 -24
  61. package/vendor/zod/classic/schemas.d.ts +17 -1
  62. package/vendor/zod/classic/schemas.js +77 -29
  63. package/vendor/zod/core/api.d.ts +6 -4
  64. package/vendor/zod/core/api.js +17 -2
  65. package/vendor/zod/core/checks.d.ts +1 -1
  66. package/vendor/zod/core/checks.js +0 -96
  67. package/vendor/zod/core/compile.d.ts +18 -4
  68. package/vendor/zod/core/compile.js +146 -67
  69. package/vendor/zod/core/doc.js +7 -2
  70. package/vendor/zod/core/errors.js +2 -5
  71. package/vendor/zod/core/json-schema-generator.js +2 -2
  72. package/vendor/zod/core/json-schema-processors.d.ts +17 -0
  73. package/vendor/zod/core/json-schema-processors.js +195 -64
  74. package/vendor/zod/core/memoizer.js +72 -23
  75. package/vendor/zod/core/parse.js +37 -16
  76. package/vendor/zod/core/regexes.d.ts +2 -0
  77. package/vendor/zod/core/regexes.js +8 -4
  78. package/vendor/zod/core/schemas.d.ts +42 -7
  79. package/vendor/zod/core/schemas.js +301 -94
  80. package/vendor/zod/core/to-json-schema.d.ts +7 -3
  81. package/vendor/zod/core/to-json-schema.js +8 -7
  82. package/vendor/zod/core/util.d.ts +25 -4
  83. package/vendor/zod/core/util.js +160 -143
  84. package/vendor/zod/core/versions.d.ts +1 -1
  85. package/vendor/zod/core/versions.js +2 -2
  86. package/vendor/zod/core/visit.js +12 -0
  87. package/vendor/zod/locales/ar.js +1 -0
  88. package/vendor/zod/locales/az.js +1 -0
  89. package/vendor/zod/locales/be.js +1 -0
  90. package/vendor/zod/locales/bg.js +1 -0
  91. package/vendor/zod/locales/bn.js +1 -0
  92. package/vendor/zod/locales/ca.js +1 -0
  93. package/vendor/zod/locales/ckb.js +1 -0
  94. package/vendor/zod/locales/cs.js +1 -0
  95. package/vendor/zod/locales/da.js +1 -0
  96. package/vendor/zod/locales/de.js +1 -0
  97. package/vendor/zod/locales/el.js +1 -0
  98. package/vendor/zod/locales/en.js +1 -0
  99. package/vendor/zod/locales/eo.js +1 -0
  100. package/vendor/zod/locales/es.js +1 -0
  101. package/vendor/zod/locales/fa.js +1 -0
  102. package/vendor/zod/locales/fi.js +1 -0
  103. package/vendor/zod/locales/fr-CA.js +1 -0
  104. package/vendor/zod/locales/fr.js +1 -0
  105. package/vendor/zod/locales/gu.js +1 -0
  106. package/vendor/zod/locales/he.js +1 -0
  107. package/vendor/zod/locales/hi.js +1 -0
  108. package/vendor/zod/locales/hr.js +1 -0
  109. package/vendor/zod/locales/hu.js +1 -0
  110. package/vendor/zod/locales/hy.js +1 -0
  111. package/vendor/zod/locales/id.js +1 -0
  112. package/vendor/zod/locales/index.d.ts +1 -0
  113. package/vendor/zod/locales/index.js +1 -0
  114. package/vendor/zod/locales/is.js +1 -0
  115. package/vendor/zod/locales/it.js +1 -0
  116. package/vendor/zod/locales/ja.js +1 -0
  117. package/vendor/zod/locales/ka.js +1 -0
  118. package/vendor/zod/locales/km.js +1 -0
  119. package/vendor/zod/locales/kn.js +1 -0
  120. package/vendor/zod/locales/ko.js +1 -0
  121. package/vendor/zod/locales/lt.js +1 -0
  122. package/vendor/zod/locales/mk.js +1 -0
  123. package/vendor/zod/locales/ms.js +1 -0
  124. package/vendor/zod/locales/ne.js +1 -0
  125. package/vendor/zod/locales/nl.js +1 -0
  126. package/vendor/zod/locales/nn.js +1 -0
  127. package/vendor/zod/locales/no.js +1 -0
  128. package/vendor/zod/locales/ota.js +1 -0
  129. package/vendor/zod/locales/pl.js +1 -0
  130. package/vendor/zod/locales/ps.js +1 -0
  131. package/vendor/zod/locales/pt-BR.js +1 -0
  132. package/vendor/zod/locales/pt.js +1 -0
  133. package/vendor/zod/locales/ro.js +1 -0
  134. package/vendor/zod/locales/ru.js +1 -0
  135. package/vendor/zod/locales/sk.js +1 -0
  136. package/vendor/zod/locales/sl.js +1 -0
  137. package/vendor/zod/locales/sv.js +1 -0
  138. package/vendor/zod/locales/ta.js +1 -0
  139. package/vendor/zod/locales/tg.d.ts +4 -0
  140. package/vendor/zod/locales/tg.js +116 -0
  141. package/vendor/zod/locales/th.js +1 -0
  142. package/vendor/zod/locales/tk.js +1 -0
  143. package/vendor/zod/locales/tr.js +1 -0
  144. package/vendor/zod/locales/uk.js +1 -0
  145. package/vendor/zod/locales/ur.js +1 -0
  146. package/vendor/zod/locales/uz.js +1 -0
  147. package/vendor/zod/locales/vi.js +1 -0
  148. package/vendor/zod/locales/yo.js +1 -0
  149. package/vendor/zod/locales/zh-CN.js +1 -0
  150. package/vendor/zod/locales/zh-TW.js +1 -0
  151. package/vendor/zod/mini/checks.d.ts +1 -1
  152. package/vendor/zod/mini/checks.js +1 -1
  153. package/vendor/zod/mini/external.d.ts +1 -1
  154. package/vendor/zod/mini/external.js +1 -1
  155. package/vendor/zod/mini/schemas.d.ts +8 -0
  156. package/vendor/zod/mini/schemas.js +19 -2
  157. package/vendor/zodexy/dist/esm/index.js +34 -1
  158. package/vendor/zodexy/dist/schema.zodexy.json +18 -0
package/eslint.config.js CHANGED
@@ -1,12 +1,15 @@
1
1
  import ashNazg from 'eslint-config-ash-nazg';
2
- // import {
3
- // parser as typescriptEslintParser
4
- // } from 'typescript-eslint';
2
+ import {
3
+ parser as typescriptEslintParser
4
+ } from 'typescript-eslint';
5
+
6
+ const typescriptEslintEnabled = false;
5
7
 
6
8
  export default [
7
9
  {
8
10
  name: 'JSOE/Ignores',
9
11
  ignores: [
12
+ '.agents',
10
13
  'coverage',
11
14
  'instrumented',
12
15
  'ignore',
@@ -17,21 +20,23 @@ export default [
17
20
  'docs'
18
21
  ]
19
22
  },
20
- // {
21
- // languageOptions: {
22
- // parser: typescriptEslintParser,
23
- // parserOptions: {
24
- // projectService: {
25
- // // eslint-disable-next-line camelcase -- API
26
- // maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 500,
27
- // allowDefaultProject: [
28
- // '*.js', '.ncurc.cjs', 'demo/*.js'
29
- // ]
30
- // },
31
- // tsconfigRootDir: import.meta.dirname
32
- // }
33
- // }
34
- // },
23
+ ...(typescriptEslintEnabled
24
+ ? [{
25
+ languageOptions: {
26
+ parser: typescriptEslintParser,
27
+ parserOptions: {
28
+ projectService: {
29
+ // eslint-disable-next-line camelcase -- API
30
+ maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 500,
31
+ allowDefaultProject: [
32
+ '*.js', '.ncurc.cjs', 'demo/*.js'
33
+ ]
34
+ },
35
+ tsconfigRootDir: import.meta.dirname
36
+ }
37
+ }
38
+ }]
39
+ : []),
35
40
  ...ashNazg(['sauron', 'browser']),
36
41
  {
37
42
  name: 'JSOE/Coverage',
@@ -106,17 +111,21 @@ export default [
106
111
 
107
112
  // Disable in ash-nazg?
108
113
  'unicorn/no-top-level-assignment-in-function': 0,
109
- 'unicorn/no-top-level-side-effects': 0
114
+ 'unicorn/no-top-level-side-effects': 0,
110
115
 
111
116
  // Good but slow
112
- // 'jsdoc/no-unnecessary-type-assertion': ['error', {
113
- // checkLiteralConstAssertions: true,
114
- // preferConstToLiteralTuples: true,
115
- // treatAnyAsRedundant: false,
116
- // typesToIgnore: [
117
- // `import('../types.js').SuperTypeObject`
118
- // ]
119
- // }]
117
+ ...(typescriptEslintEnabled
118
+ ? {
119
+ 'jsdoc/no-unnecessary-type-assertion': ['error', {
120
+ checkLiteralConstAssertions: true,
121
+ preferConstToLiteralTuples: true,
122
+ treatAnyAsRedundant: false,
123
+ typesToIgnore: [
124
+ `import('../types.js').SuperTypeObject`
125
+ ]
126
+ }]
127
+ }
128
+ : {})
120
129
  }
121
130
  }
122
131
  ];
package/mmr.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "mochaBadgeGeneratorReporterOptions": {
3
+ "badge_output": "badges/tests-badge.svg"
4
+ },
5
+ "reporterEnabled": "spec, mocha-badge-generator"
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@es-joy/jsoe",
3
- "version": "0.26.1",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./src/index.js",
@@ -28,14 +28,19 @@
28
28
  ]
29
29
  },
30
30
  "dependencies": {
31
- "@node-static/node-static": "^0.9.1",
31
+ "@codemirror/lang-javascript": "^6.2.5",
32
+ "@codemirror/state": "^6.7.4",
33
+ "@marijn/find-cluster-break": "^1.0.4",
34
+ "@node-static/node-static": "^0.9.2",
32
35
  "acorn": "^8.18.0",
33
- "jamilih": "0.69.0",
36
+ "codemirror": "^6.0.2",
37
+ "jamilih": "0.69.1",
38
+ "json-6": "^1.1.4",
34
39
  "mime": "^4.1.0",
35
40
  "sceditor": "3.2.1",
36
- "typeson-registry": "14.5.0",
37
- "zod": "^4.5.4",
38
- "zodexy": "^0.30.1"
41
+ "typeson-registry": "14.6.1",
42
+ "zod": "^4.6.1",
43
+ "zodexy": "^0.31.0"
39
44
  },
40
45
  "devDependencies": {
41
46
  "@babel/core": "^8.0.1",
@@ -55,11 +60,13 @@
55
60
  "cypress": "^16.0.0",
56
61
  "cypress-axe": "^1.7.0",
57
62
  "cypress-terminal-report": "^7.3.3",
58
- "eslint": "^10.9.1",
59
- "eslint-config-ash-nazg": "^43.1.6",
63
+ "eslint": "^10.10.0",
64
+ "eslint-config-ash-nazg": "^43.1.8",
60
65
  "fast-deep-equal": "^3.1.3",
61
66
  "jsdom": "^30.0.1",
62
67
  "license-badger": "^0.23.0",
68
+ "mocha-badge-generator": "^0.13.0",
69
+ "mocha-multi-reporters": "^1.5.2",
63
70
  "npm-run-all": "^4.1.5",
64
71
  "nyc": "^18.0.0",
65
72
  "open-cli": "^9.0.0",
@@ -68,7 +75,7 @@
68
75
  "rollup-plugin-istanbul": "^5.0.0",
69
76
  "rollup-plugin-node-builtins": "^2.1.2",
70
77
  "typescript": "^6.0.3",
71
- "typescript-eslint": "^8.69.0",
78
+ "typescript-eslint": "^8.70.0",
72
79
  "typeson": "^10.3.0",
73
80
  "webappfind-demos-samples": "^0.7.0"
74
81
  },
@@ -115,12 +122,15 @@
115
122
  "copy-zodexy": "cp -R node_modules/zodexy/dist/esm vendor/zodexy/dist && cp node_modules/zodexy/dist/schema.zodexy.json vendor/zodexy/dist/schema.zodexy.json",
116
123
  "copy-acorn": "cp -R node_modules/acorn/dist vendor/acorn",
117
124
  "copy-mime": "mkdir -p vendor/mime/dist/types && cp node_modules/mime/dist/types/standard.js vendor/mime/dist/types/standard.js && cp node_modules/mime/dist/types/other.js vendor/mime/dist/types/other.js",
118
- "copy": "run-p copy-jamilih copy-typeson-registry copy-zod copy-zodexy copy-acorn copy-mime",
125
+ "copy-json6": "mkdir -p vendor/json-6/dist && cp node_modules/json-6/dist/index.mjs vendor/json-6/dist/index.mjs",
126
+ "copy-codemirror": "mkdir -p vendor/codemirror/dist vendor/@codemirror/lang-javascript/dist vendor/@codemirror/state/dist vendor/@codemirror/view/dist vendor/@codemirror/language/dist vendor/@codemirror/commands/dist vendor/@codemirror/search/dist vendor/@codemirror/autocomplete/dist vendor/@codemirror/lint/dist vendor/@lezer/common/dist vendor/@lezer/highlight/dist vendor/@lezer/lr/dist vendor/@lezer/javascript/dist vendor/@marijn/find-cluster-break/src vendor/crelt vendor/style-mod/src vendor/w3c-keyname && cp node_modules/codemirror/dist/index.js vendor/codemirror/dist/index.js && cp node_modules/@codemirror/lang-javascript/dist/index.js vendor/@codemirror/lang-javascript/dist/index.js && cp node_modules/@codemirror/state/dist/index.js vendor/@codemirror/state/dist/index.js && cp node_modules/@codemirror/view/dist/index.js vendor/@codemirror/view/dist/index.js && cp node_modules/@codemirror/language/dist/index.js vendor/@codemirror/language/dist/index.js && cp node_modules/@codemirror/commands/dist/index.js vendor/@codemirror/commands/dist/index.js && cp node_modules/@codemirror/search/dist/index.js vendor/@codemirror/search/dist/index.js && cp node_modules/@codemirror/autocomplete/dist/index.js vendor/@codemirror/autocomplete/dist/index.js && cp node_modules/@codemirror/lint/dist/index.js vendor/@codemirror/lint/dist/index.js && cp node_modules/@lezer/common/dist/index.js vendor/@lezer/common/dist/index.js && cp node_modules/@lezer/highlight/dist/index.js vendor/@lezer/highlight/dist/index.js && cp node_modules/@lezer/lr/dist/index.js vendor/@lezer/lr/dist/index.js && cp node_modules/@lezer/javascript/dist/index.js vendor/@lezer/javascript/dist/index.js && cp node_modules/@marijn/find-cluster-break/src/index.js vendor/@marijn/find-cluster-break/src/index.js && cp node_modules/crelt/index.js vendor/crelt/index.js && cp node_modules/style-mod/src/style-mod.js vendor/style-mod/src/style-mod.js && cp node_modules/w3c-keyname/index.js vendor/w3c-keyname/index.js",
127
+ "copy": "run-p copy-jamilih copy-typeson-registry copy-zod copy-zodexy copy-acorn copy-mime copy-json6 copy-codemirror",
119
128
  "instrument": "NODE_OPTIONS=--max-old-space-size=8192 run-s -c instrument-remove instrument-add copy-index rollup",
120
129
  "coveradge": "coveradge badges/coverage-badge.svg",
121
130
  "cypress:log": "ELECTRON_ENABLE_LOGGING=1 cypress run; npm run report",
122
- "cypress": "cypress run --spec cypress/e2e/all.cy.js; npm run report && npm run coveradge",
123
- "cypress:record": "cypress run --record --parallel",
131
+ "cypress": "rm -Rf .nyc_output && cypress run --browser chrome --reporter=mocha-multi-reporters --reporter-options configFile=mmr.json --spec cypress/e2e/all.cy.js; npm run report && npm run coveradge",
132
+ "cypress:record": "cypress run --browser chrome --record --parallel",
133
+ "cypress:sample-spec": "rm -Rf .nyc_output && cypress run --browser chrome --reporter=mocha-multi-reporters --reporter-options configFile=mmr.json --spec cypress/e2e/subTypes/true.cy.js; npm run report && npm run coveradge",
124
134
  "report": "nyc report",
125
135
  "test": "npm run eslint && npm run rollup && npm run cypress"
126
136
  }
@@ -13,7 +13,7 @@ minimumReleaseAgeExclude:
13
13
  - eslint-config-ash-nazg@42.3.0 || 42.5.0 || 43.0.1 || 43.1.0 || 43.1.2 || 43.1.5 || 43.1.6
14
14
  - eslint-plugin-jsdoc@64.1.0 || 64.2.1 || 64.3.0 || 64.3.2 || 64.3.3
15
15
  - jsdoc-type-pratt-parser@9.1.1 || 9.1.2
16
- - zodexy@0.21.1 || 0.21.2 || 0.22.0 || 0.23.0 || 0.24.0 || 0.24.1 || 0.25.0 || 0.26.0 || 0.26.1 || 0.27.0 || 0.30.0 || 0.30.1
16
+ - zodexy@0.21.1 || 0.21.2 || 0.22.0 || 0.23.0 || 0.24.0 || 0.24.1 || 0.25.0 || 0.26.0 || 0.26.1 || 0.27.0 || 0.30.0 || 0.30.1 || 0.31.0
17
17
  - typeson-registry@13.0.1 || 13.1.0 || 14.4.0 || 14.5.0
18
18
  - typeson@10.1.1 || 10.2.0 || 10.3.0
19
19
  - '@rollup/rollup-android-arm-eabi@4.63.1'
@@ -43,7 +43,7 @@ minimumReleaseAgeExclude:
43
43
  - '@rollup/rollup-win32-x64-msvc@4.63.1'
44
44
  - eslint-plugin-escompat@3.11.6 || 3.12.0
45
45
  - rollup@4.63.1
46
- - zod@4.5.2 || 4.5.4
46
+ - zod@4.5.2 || 4.5.4 || 4.6.1
47
47
  - '@typescript-eslint/eslint-plugin@8.69.0'
48
48
  - '@typescript-eslint/parser@8.69.0'
49
49
  - '@typescript-eslint/project-service@8.69.0'
@@ -55,3 +55,5 @@ minimumReleaseAgeExclude:
55
55
  - '@typescript-eslint/visitor-keys@8.69.0'
56
56
  - typescript-eslint@8.69.0
57
57
  - '@types/node@26.4.1'
58
+ - '@node-static/node-static@0.9.2'
59
+ - mocha-multi-reporters@1.5.2
@@ -43,6 +43,10 @@ const zodexToStructuredCloningTypeMap = new Map([
43
43
  ['array', 'arrayNonindexKeys'],
44
44
 
45
45
  ['object', 'object'],
46
+ // `z.properties()` (Zod 4.5+) is a bare named-property shape: like `object`
47
+ // but with no `catchall`/`symbols` and, in Zod, no unknown-key stripping.
48
+ // jsoe edits it through the same `object` UI, keyed off the schema `type`.
49
+ ['properties', 'object'],
46
50
 
47
51
  ['tuple', 'array'],
48
52
  ['record', 'object'],
@@ -190,13 +194,15 @@ const dezerializerCodecs = {
190
194
  };
191
195
 
192
196
  /**
197
+ * Exported for reuse by `src/utils/rawTypesonEditor.js`, which must enforce
198
+ * the same schema conformance check before accepting a raw-edited value.
193
199
  * @param {InstanceType<typeof import('../types.js').default>} types
194
200
  * @param {ZodexSchema} schemaObject
195
201
  * @param {ZodexSchema} originalShape
196
202
  * @param {unknown} value
197
203
  * @returns {ReturnType<ReturnType<typeof dezerialize>['safeParse']>}
198
204
  */
199
- function parseValue (types, schemaObject, originalShape, value) {
205
+ export function parseValue (types, schemaObject, originalShape, value) {
200
206
  return dezerialize(schemaObject, {
201
207
  checks: getChecks(types),
202
208
  codecs: dezerializerCodecs,
@@ -380,6 +386,17 @@ function mergeMeta (target, source) {
380
386
  return merged;
381
387
  }
382
388
 
389
+ /**
390
+ * Whether a schema node carries a fixed bag of named property schemas under
391
+ * `properties` - `object` and `z.properties()`. Both are merged the same way
392
+ * across an intersection and both drive the `object` editor UI.
393
+ * @param {ZodexSchema['type']|undefined} type
394
+ * @returns {boolean}
395
+ */
396
+ function isPropertyBagType (type) {
397
+ return type === 'object' || type === 'properties';
398
+ }
399
+
383
400
  /**
384
401
  * @param {ZodexSchema} leftItem
385
402
  * @param {ZodexSchema} rightItem
@@ -412,7 +429,7 @@ function mergeSchema (leftItem, rightItem) {
412
429
  );
413
430
  } else if (Object.hasOwn(newLeftObj, prop)) {
414
431
  if (newLeftObj[prop] !== val && !deepEqual(newLeftObj[prop], val)) {
415
- if (leftItem.type === 'object') {
432
+ if (isPropertyBagType(leftItem.type)) {
416
433
  throw new Error(
417
434
  'Duplicate property ' + prop + ' of value ' +
418
435
  JSON.stringify(val) + ' and ' +
@@ -450,12 +467,14 @@ function mergeSchema (leftItem, rightItem) {
450
467
  }
451
468
  }
452
469
 
453
- if (leftItem.type !== 'object' || rightItem.type !== 'object') {
470
+ if (!isPropertyBagType(leftItem.type) || !isPropertyBagType(rightItem.type)) {
454
471
  delete newLeftObj.error;
455
472
  return newLeftObj;
456
473
  }
457
474
 
458
- for (const [prop, val] of Object.entries(rightItem.properties)) {
475
+ for (const [prop, val] of Object.entries(
476
+ /** @type {import('zodexy').SzObject} */ (rightItem).properties
477
+ )) {
459
478
  if (typeof newLeftObj.properties !== 'string' &&
460
479
  Object.hasOwn(newLeftObj.properties, prop)) {
461
480
  if (deepEqual(newLeftObj.properties[prop], val)) {
@@ -594,6 +613,7 @@ export function getTypesForSchema (schemaObject, originalJSON) {
594
613
  originalJSON
595
614
  );
596
615
  }
616
+ case 'properties':
597
617
  case 'object': {
598
618
  const set = new Set();
599
619
  // const {properties} = schemaObject;
@@ -859,7 +879,9 @@ const schema = {
859
879
  if (['record', 'looseRecord', 'tuple'].includes(schemaObject.type)) {
860
880
  return true;
861
881
  }
862
- return schemaObject.type !== 'object' &&
882
+ // `properties` is a named-property shape like `object`: the per-property
883
+ // controls carry their own schemas, so no value-level reparse is needed.
884
+ return !isPropertyBagType(schemaObject.type) &&
863
885
  intersectionSchemas.has(schemaObject);
864
886
  },
865
887
  validateValue (types, schemaObject, value) {
@@ -914,16 +936,25 @@ const schema = {
914
936
 
915
937
  switch (parentSchema?.type) {
916
938
  case 'object':
939
+ case 'properties':
940
+ // A key outside the declared shape: an `object` pins it to its
941
+ // `catchall` if it has one; otherwise (and always for `properties`,
942
+ // which has no `catchall`) its control is offered the unconstrained
943
+ // type choice - as a `looseRecord`'s non-conforming entry is - rather
944
+ // than the bare "unschema'd" fallback. A strict `object` will still
945
+ // strip the key on parse; this only governs how it is edited.
917
946
  currentSchema = /** @type {import('zodexy').SzObject} */ (
918
947
  parentSchema
919
948
  ).properties[
920
949
  /** @type {string} */ (arrayOrObjectPropertyName)
921
950
  ];
922
951
  if (!currentSchema) {
952
+ mustBeOptional = true;
923
953
  currentSchema = /** @type {import('zodexy').SzObject} */ (
924
954
  parentSchema
925
- ).catchall;
926
- mustBeOptional = true;
955
+ ).catchall ?? /** @type {import('zodexy').SzType} */ (
956
+ {type: 'unknown'}
957
+ );
927
958
  }
928
959
  break;
929
960
  case 'array':
@@ -274,23 +274,30 @@ const encapsulateObserver = (stateObj) => {
274
274
  // console.log('but could set to', schema);
275
275
  // console.log('arrayOrObjectPropertyName', arrayOrObjectPropertyName);
276
276
 
277
- if (!stateObj.rootUI) {
278
- // console.log('vvvv0', newType, newValue);
279
- stateObj.rootUI = types?.getUIForModeAndType({
280
- readonly,
281
- typeNamespace,
282
- type: newType,
283
- bringIntoFocus: false,
284
- buildTypeChoices,
285
- format,
286
- specificSchemaObject: schema,
287
- schemaContent,
288
- value: newValue,
289
- hasValue: true,
290
- // Not currently in use but may be convenient for a
291
- // type wanting the serialized data
292
- replaced
293
- });
277
+ if (!parents['']) {
278
+ // A caller repopulating an *existing* container in place (e.g. the raw
279
+ // Typeson/JSON6 editor replacing one control's children after a
280
+ // bulk edit, `src/utils/rawTypesonEditor.js`) pre-seeds
281
+ // `stateObj.rootUI` with that container so it is reused here instead
282
+ // of a fresh one being built.
283
+ if (!stateObj.rootUI) {
284
+ // console.log('vvvv0', newType, newValue);
285
+ stateObj.rootUI = types?.getUIForModeAndType({
286
+ readonly,
287
+ typeNamespace,
288
+ type: newType,
289
+ bringIntoFocus: false,
290
+ buildTypeChoices,
291
+ format,
292
+ specificSchemaObject: schema,
293
+ schemaContent,
294
+ value: newValue,
295
+ hasValue: true,
296
+ // Not currently in use but may be convenient for a
297
+ // type wanting the serialized data
298
+ replaced
299
+ });
300
+ }
294
301
  parents[''] = /**
295
302
  * @type {HTMLElement &
296
303
  * {$addAndSetArrayElement: AddAndSetArrayElement}}
@@ -7,6 +7,7 @@ import {
7
7
  schemaLabel, lastTypeChild, deprecatedClassSuffix
8
8
  } from '../utils/schemaMeta.js';
9
9
  import dialogs from '../utils/dialogs.js';
10
+ import {openRawEditorDialog} from '../utils/rawTypesonEditor.js';
10
11
  import {
11
12
  resolveJSONPointer, getJSONPointerParts, reduceJSONPointerParts
12
13
  } from '../utils/jsonPointer.js';
@@ -428,6 +429,13 @@ const arrayType = {
428
429
  specificSchemaObject?.type === 'looseRecord';
429
430
  const tupleMode = specificSchemaObject?.type === 'tuple';
430
431
 
432
+ // See `docs/proposals/raw-typeson-edit-view.md` §1 for the scope
433
+ // rationale (why `filelist` is included, why record/tuple count).
434
+ const isRawEditable = types.showRawTypesonControls &&
435
+ ((type === 'object' || type === 'array' ||
436
+ type === 'set' || type === 'map' || type === 'filelist') ||
437
+ recordMode || tupleMode);
438
+
431
439
  /**
432
440
  * @param {{
433
441
  * itemIndex: number,
@@ -660,6 +668,31 @@ const arrayType = {
660
668
  target
661
669
  ).textContent = arrayContents.hidden ? '+' : '-';
662
670
  }}}, ['-']],
671
+ ...(isRawEditable
672
+ ? /** @type {import('jamilih').JamilihArray[]} */ ([
673
+ nbsp.repeat(2),
674
+ ['button', {
675
+ class: 'viewRawTypeson',
676
+ $on: {click (/** @type {Event} */ e) {
677
+ e.preventDefault();
678
+ openRawEditorDialog({
679
+ types,
680
+ format: /** @type {import('../formats.js').AvailableFormat} */ (
681
+ format
682
+ ),
683
+ type: /** @type {import('../types.js').AvailableArbitraryType} */ (
684
+ type
685
+ ),
686
+ root: /** @type {HTMLDivElement} */ (div),
687
+ topRoot: /** @type {HTMLDivElement} */ (topRoot),
688
+ typeNamespace,
689
+ specificSchemaObject,
690
+ readonly: true
691
+ });
692
+ }}
693
+ }, ['View raw']]
694
+ ])
695
+ : []),
663
696
  ['div', {class: 'arrayContents'}, [
664
697
  !recordMode && this.array
665
698
  ? ['div', {
@@ -724,6 +757,13 @@ const arrayType = {
724
757
  specificSchemaObject?.type === 'looseRecord';
725
758
  const tupleMode = specificSchemaObject?.type === 'tuple';
726
759
 
760
+ // See `docs/proposals/raw-typeson-edit-view.md` §1 for the scope
761
+ // rationale (why `filelist` is included, why record/tuple count).
762
+ const isRawEditable = types.showRawTypesonControls &&
763
+ ((type === 'object' || type === 'array' ||
764
+ type === 'set' || type === 'map' || type === 'filelist') ||
765
+ recordMode || tupleMode);
766
+
727
767
  // A `record` refines `object` but, for legend numbering/reordering, behaves
728
768
  // like the array-based types did when it was its own (`array: true`) type.
729
769
  const parentIsArrayLike = parentTypeObject.array || recordMode;
@@ -1628,9 +1668,27 @@ const arrayType = {
1628
1668
  specificSchemaObject
1629
1669
  )?.element;
1630
1670
  }
1631
- return /** @type {import('zodexy').SzObject} */ (
1671
+ const objectSchema = /** @type {import('zodexy').SzObject} */ (
1632
1672
  specificSchemaObject
1633
- )?.properties?.[/** @type {string} */ (propName)];
1673
+ );
1674
+ const schemaType = specificSchemaObject?.type;
1675
+ const propSchema = objectSchema?.properties?.[/** @type {string} */ (
1676
+ propName
1677
+ )];
1678
+ if (
1679
+ !propSchema &&
1680
+ (schemaType === 'properties' ||
1681
+ (schemaType === 'object' && !objectSchema.catchall))
1682
+ ) {
1683
+ // A key outside the declared shape of a `z.properties()` (which keeps
1684
+ // it) or a `catchall`-less `z.object()` (which strips it on parse but
1685
+ // still shows it while editing): offer the unconstrained type choice
1686
+ // rather than an unschema'd control, as a `looseRecord`'s
1687
+ // non-conforming entry gets. An `object` `catchall` takes precedence
1688
+ // (handled where the per-property schema is chosen elsewhere).
1689
+ return /** @type {import('zodexy').SzType} */ ({type: 'unknown'});
1690
+ }
1691
+ return propSchema;
1634
1692
  };
1635
1693
 
1636
1694
  /**
@@ -2340,6 +2398,34 @@ const arrayType = {
2340
2398
  ).textContent = arrayContents.hidden ? '+' : '-';
2341
2399
  }}}, ['-']];
2342
2400
 
2401
+ const rawEditButton = isRawEditable
2402
+ ? [
2403
+ nbsp.repeat(2),
2404
+ ['button', {
2405
+ class: 'editRawTypeson',
2406
+ $on: {click (/** @type {Event} */ e) {
2407
+ e.preventDefault();
2408
+ openRawEditorDialog({
2409
+ types,
2410
+ format: /** @type {import('../formats.js').AvailableFormat} */ (
2411
+ format
2412
+ ),
2413
+ type: /** @type {import('../types.js').AvailableArbitraryType} */ (
2414
+ type
2415
+ ),
2416
+ root: /** @type {HTMLDivElement} */ (div),
2417
+ // By click time `topRoot ||= div` below has already run if
2418
+ // this control is itself the root.
2419
+ topRoot: /** @type {HTMLDivElement} */ (topRoot),
2420
+ typeNamespace,
2421
+ specificSchemaObject,
2422
+ readonly: false
2423
+ });
2424
+ }}
2425
+ }, ['Edit raw']]
2426
+ ]
2427
+ : [];
2428
+
2343
2429
  const arrayContents = /** @type {import('jamilih').JamilihArray} */ (
2344
2430
  ['div', {class: 'arrayContents'}, [
2345
2431
  arrayContentsFirstChild,
@@ -2415,7 +2501,8 @@ const arrayType = {
2415
2501
  * $getTypeChoices: () => HTMLSelectElement & {
2416
2502
  * $setType: import('../typeChoices.js').SetType,
2417
2503
  * $getTypeRoot: import('../formatAndTypeChoices.js').TypeRootGetter
2418
- * }
2504
+ * },
2505
+ * $resetItemIndex: () => void
2419
2506
  * }} DivArrayOrObjectHolder
2420
2507
  */
2421
2508
 
@@ -2567,6 +2654,40 @@ const arrayType = {
2567
2654
  this.$getAddArrayElement().previousElementSibling
2568
2655
  );
2569
2656
  },
2657
+ /**
2658
+ * Resets the `itemIndex`/legend-numbering counter back to its
2659
+ * empty-container baseline (`itemAdjust - 1`, the same value
2660
+ * it's initialized to before any item is ever added — *not*
2661
+ * the `-1` the "x All" button below hardcodes, which is only
2662
+ * correct for arrays; an object's baseline is `0`). Exposed so
2663
+ * a caller that repopulates this container's children from
2664
+ * outside this closure (bypassing the normal per-item
2665
+ * add/remove handlers that keep `itemIndex` in sync as they
2666
+ * go) — currently only
2667
+ * `src/utils/rawTypesonEditor.js`'s `commitValueToContainer`,
2668
+ * after a raw-value edit replaces every child — ends up with
2669
+ * exactly the same numbering a fresh container holding the
2670
+ * same new items would have, rather than continuing from
2671
+ * wherever the replaced content had left off (or, using the
2672
+ * wrong baseline, starting one lower than a fresh container
2673
+ * would: a single-property object's only property would be
2674
+ * renumbered from "1" to "0" by a bulk edit that didn't even
2675
+ * change its key, for example).
2676
+ * @this {DivArrayOrObjectHolder}
2677
+ * @returns {void}
2678
+ */
2679
+ $resetItemIndex () {
2680
+ if (sparse) {
2681
+ decrementItemIndex(
2682
+ /**
2683
+ * @type {HTMLDivElement & {
2684
+ * $getPropertyInputs: GetPropertyInputs
2685
+ }} */ (this.$getArrayItems())
2686
+ );
2687
+ } else {
2688
+ itemIndex = itemAdjust - 1;
2689
+ }
2690
+ },
2570
2691
  /**
2571
2692
  * @this {DivArrayOrObjectHolder}
2572
2693
  * @returns {HTMLSelectElement & {
@@ -2612,10 +2733,16 @@ const arrayType = {
2612
2733
  }
2613
2734
  }, /** @type {import('jamilih').JamilihChildren} */ ([
2614
2735
  [specificSchemaObject ? 'span' : 'b', {
2615
- title: schemaLabel(specificSchemaObject) ?? (DOM.initialCaps(
2616
- /** @type {import('../types.js').AvailableType} */
2617
- (itemType)
2618
- ).replace(/s$/u, ''))
2736
+ title: schemaLabel(specificSchemaObject) ?? (
2737
+ // `z.properties()` renders through the `object` UI; without a
2738
+ // `meta` label of its own, name it "Properties" rather than an
2739
+ // indistinguishable "Object".
2740
+ specificSchemaObject?.type === 'properties'
2741
+ ? 'Properties'
2742
+ : DOM.initialCaps(
2743
+ /** @type {import('../types.js').AvailableType} */
2744
+ (itemType)
2745
+ ).replace(/s$/u, ''))
2619
2746
  }, [
2620
2747
  specificSchemaObject
2621
2748
  ? '—'
@@ -2660,6 +2787,7 @@ const arrayType = {
2660
2787
  }]
2661
2788
  : '',
2662
2789
  minusButton,
2790
+ ...rawEditButton,
2663
2791
  arrayContents
2664
2792
  ]))
2665
2793
  );
@@ -133,6 +133,11 @@ const stringType = {
133
133
  return `Value doesn't match credit card pattern.`;
134
134
  }
135
135
  break;
136
+ case 'iban':
137
+ if (!regexes.iban.test(value)) {
138
+ return `Value doesn't match iban pattern.`;
139
+ }
140
+ break;
136
141
  case 'ip':
137
142
  switch ('version' in stringSchemaObject &&
138
143
  stringSchemaObject.version) {
package/src/jsoe.css CHANGED
@@ -139,6 +139,18 @@ video.recordedMedia {
139
139
  height: 300px;
140
140
  }
141
141
 
142
+ .jsoe-raw-editor {
143
+ width: 500px;
144
+ max-width: 100%;
145
+ height: 300px;
146
+ overflow: auto;
147
+ border: 1px solid;
148
+ }
149
+
150
+ .jsoe-raw-editor-error:not(:empty) {
151
+ color: #b00;
152
+ }
153
+
142
154
  .visualizer {
143
155
  height: 60px;
144
156
  }