ivue 2.4.0 → 2.6.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.
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `nestedProps(props, defaults)` — fill a nested object prop from its
3
+ * defaults, in place, so the class reads complete props at every depth.
4
+ *
5
+ * Vue resolves a prop's default only when the prop is ABSENT: pass
6
+ * `{ wheel: { gain: 2 } }` for a prop whose default is
7
+ * `{ wheel: { gain: 1, follow: 0.1 }, touch: { … } }` and the component
8
+ * receives exactly the object it was passed — `follow` and `touch` are
9
+ * gone. `propsWithDefaults` decides what the default IS and clones it per
10
+ * instance; it cannot reach inside a supplied object. The fill is possible
11
+ * at all because the defaults are a value the class owns — a compiler-only
12
+ * default has nothing to fill from.
13
+ *
14
+ * The semantics are lodash's `defaultsDeep` with arrays taken whole: for
15
+ * every prop whose value and default are both plain objects, each leaf the
16
+ * supplied object lacks is written into it from the default, recursively;
17
+ * a leaf it has is kept. Missing plain-object and array defaults are copied
18
+ * recursively so instances never share those mutable containers. Arrays
19
+ * are never merged; class instances and functions retain their identity. Vue's
20
+ * props proxy is shallow, so the nested objects are the parent's own and
21
+ * are written directly; the props object itself is untouched and returned.
22
+ *
23
+ * Call it once, at the seam where props enter the class:
24
+ *
25
+ * constructor(props: Scroller.Props, public emit: Scroller.Emits) {
26
+ * this.props = nestedProps(props, this.self.propsDefaults);
27
+ * }
28
+ *
29
+ * The parent passes a stable object: a constant inline literal (Vue hoists
30
+ * it), a `ref`'s value, a store field. An object built anew on every
31
+ * parent render is a new, unfilled object each time.
32
+ *
33
+ * `customCloner` is the same policy knob `propsWithDefaults` has: it copies
34
+ * each default branch written into the props. The default, `clone`, copies
35
+ * plain containers and keeps callbacks, constructors and opaque objects by
36
+ * reference, so no two instances share a mutable default. Pass
37
+ * `structuredClone` when `Date`, `Map` or `Set` defaults need their own
38
+ * copies; pass the identity, `value => value`, only when the caller owns
39
+ * every default it passes — a fresh tree per instance, never a shared one.
40
+ *
41
+ * `NestedPartial<T>` is the matching declaration for the prop's type — the
42
+ * shape an author may pass — and `NestedProps<P, D>` the type of the
43
+ * filled props, where every key both sides carry as an object is complete.
44
+ *
45
+ * Ships from `ivue/extras` (not the reactive core) so the primary `ivue`
46
+ * entry stays minimal.
47
+ */
48
+
49
+ import { clone } from './clone';
50
+
51
+ /** Every key optional at every plain-object depth; arrays stay whole. */
52
+ export type NestedPartial<T> = T extends readonly unknown[]
53
+ ? T
54
+ : T extends object
55
+ ? { [K in keyof T]?: NestedPartial<T[K]> }
56
+ : T;
57
+
58
+ /** The filled props' type: a key both sides carry as a plain object is complete. */
59
+ export type NestedProps<P, D> = {
60
+ [K in keyof P as K extends keyof D ? K : never]-?: NestedLeaf<
61
+ NonNullable<P[K]>,
62
+ D[K & keyof D]
63
+ >;
64
+ } & {
65
+ [K in keyof P as K extends keyof D ? never : K]: P[K];
66
+ };
67
+
68
+ /** A value the fill takes whole: not a plain container. */
69
+ type Opaque = Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never[]) => unknown);
70
+
71
+ type NestedLeaf<V, D> = V extends readonly unknown[]
72
+ ? V
73
+ : V extends Opaque
74
+ ? V
75
+ : V extends object
76
+ ? D extends readonly unknown[]
77
+ ? V
78
+ : D extends object
79
+ ? NestedProps<V, D>
80
+ : V
81
+ : V;
82
+
83
+ type PlainObject = Record<PropertyKey, unknown>;
84
+
85
+ /** A plain object — an object literal, `Object.create(null)`, or a reactive
86
+ * proxy over either; not an array, a class instance or a function. A
87
+ * `constructor` check, no prototype walk. */
88
+ function isPlain(value: unknown): value is PlainObject {
89
+ return (
90
+ value !== null &&
91
+ typeof value === 'object' &&
92
+ ((value as PlainObject).constructor === Object ||
93
+ (value as PlainObject).constructor === undefined)
94
+ );
95
+ }
96
+
97
+ /** Write every leaf `target` lacks from `defaults`, recursively; keep what
98
+ * it has. `for…in` over the defaults: plain objects have no enumerable
99
+ * prototype keys. */
100
+ function fill(target: PlainObject, defaults: PlainObject, copy: (value: unknown) => unknown) {
101
+ for (const key in defaults) {
102
+ const value = target[key];
103
+ const fallback = defaults[key];
104
+ if (value === undefined) target[key] = copy(fallback);
105
+ else if (isPlain(value) && isPlain(fallback)) fill(value, fallback, copy);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Fill every nested object prop from its default, in place, and return
111
+ * the props typed as complete. Top-level props are Vue's to default and
112
+ * are never written.
113
+ */
114
+ export function nestedProps<P extends object, D extends object>(
115
+ props: P,
116
+ defaults: D,
117
+ // Optional: the copy policy for default branches written into the props.
118
+ customCloner: (value: unknown) => unknown = clone
119
+ ): NestedProps<P, D> {
120
+ for (const key in defaults) {
121
+ const value = (props as PlainObject)[key];
122
+ const fallback = (defaults as PlainObject)[key];
123
+ if (isPlain(value) && isPlain(fallback)) fill(value, fallback, customCloner);
124
+ }
125
+ return props as unknown as NestedProps<P, D>;
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ivue",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "Infinite Vue – Class Based Architecture for Vue 3",
5
5
  "type": "module",
6
6
  "exports": {
@@ -14,12 +14,18 @@
14
14
  "import": "./dist/extras.es.js",
15
15
  "require": "./dist/extras.cjs"
16
16
  },
17
- "./package.json": "./package.json"
17
+ "./package.json": "./package.json",
18
+ "./skills/*": "./skills/*"
18
19
  },
19
20
  "main": "./dist/index.cjs",
20
21
  "module": "./dist/index.es.js",
21
22
  "types": "./dist/index.d.ts",
22
23
  "scripts": {
24
+ "gate": "vite-node skills/ivue/ivue-standards-check.ts --",
25
+ "gate:docs": "node scripts/gate-docs.mjs",
26
+ "sweep:components": "NODE_PATH=$PWD/node_modules node docs_v2/scripts/component-sweep.cjs",
27
+ "gate:house": "vite-node skills/ivue/ivue-house-gate.ts --",
28
+ "gate:newsletter": "vite-node skills/ivue/ivue-generator-standard.ts -- --source-root newsletter --skip-list skills/ivue/ivue-standards-skip.json --test-glob 'newsletter/src/**/*.test.ts'",
23
29
  "dev": "vite demo --host",
24
30
  "dev:demo": "vite demo --host",
25
31
  "dev:flyweight": "vite examples/playground --port 5181 --host",
@@ -37,9 +43,9 @@
37
43
  "bench:node-namespace": "vite-node experiments/node-namespace/benchmark.ts",
38
44
  "cypress": "cypress run --component",
39
45
  "cypress:headed": "cypress run --component --browser chrome --headed --no-exit",
40
- "build": "tsc --version;tsc --p ./tsconfig.json && vite build",
46
+ "build": "tsc --version;tsc --p ./tsconfig.json && node scripts/build-lib.mjs",
41
47
  "build:demo": "tsc --p ./tsconfig.json && vite build demo",
42
- "build:docs": "npm run sync:examples && npm run sync:releases && npm run sync:blog-index && npm --prefix docs_v2 run build && npm run check:links && node docs_v2/scripts/check-related-posts.mjs",
48
+ "build:docs": "npm run gate:docs && npm run sync:examples && npm run sync:releases && npm run sync:blog-index && node docs_v2/scripts/rss-generator.mjs && npm --prefix docs_v2 run build && npm run check:links && node docs_v2/scripts/check-related-posts.mjs && node docs_v2/scripts/check-private-ban.mjs",
43
49
  "sync:releases": "node docs_v2/scripts/releases-page-generator.mjs",
44
50
  "sync:blog-index": "node docs_v2/scripts/blog-index-generator.mjs",
45
51
  "check:links": "node docs_v2/scripts/check-links.mjs",
@@ -56,7 +62,7 @@
56
62
  "release": "npm run build && npm publish",
57
63
  "sync:skill": "node -e \"const fs=require('fs');fs.mkdirSync('skills/ivue',{recursive:true});fs.copyFileSync('.claude/skills/ivue/SKILL.md','skills/ivue/SKILL.md')\"",
58
64
  "prepack": "npm run sync:skill",
59
- "sync:examples": "node -e \"require('fs').copyFileSync('lib/Reactive.ts','examples/playground/src/ivue.ts')\"",
65
+ "sync:examples": "node scripts/sync-examples.mjs",
60
66
  "dev:playground": "vite examples/playground --host",
61
67
  "build:playground": "vite build examples/playground",
62
68
  "preview:playground": "vite preview examples/playground --host",
@@ -102,7 +108,9 @@
102
108
  "rollup-plugin-terser": "^7.0.2",
103
109
  "typescript": "^5.9.3",
104
110
  "vite": "^4.0.4",
111
+ "vite-node": "^2.0.5",
105
112
  "vite-plugin-dts": "^1.7.1",
113
+ "@microsoft/api-extractor": "7.33.8",
106
114
  "vite-plugin-dynamic-import": "^1.5.0",
107
115
  "vite-tsconfig-paths": "^4.3.2",
108
116
  "vitest": "^2.0.5",