@burgantech/pseudo-ui 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -35,97 +35,294 @@ npm install vue@^3.5 primevue@^4.5 @primeuix/themes@^2 primeicons@^7
35
35
 
36
36
  ## Quick Start
37
37
 
38
- ### 1. Set up the delegate
38
+ Two steps: first a minimal form, then we enrich it with a nested component and a dropdown. Each step is self-contained.
39
39
 
40
- The SDK uses **inversion of control** — you implement `PseudoViewDelegate` to supply data, load nested components, and handle actions:
40
+ ---
41
41
 
42
- ```typescript
43
- import type { PseudoViewDelegate } from '@burgantech/pseudo-ui'
42
+ ### Step 1: Minimal form
44
43
 
45
- const delegate: PseudoViewDelegate = {
46
- async requestData(ref, params) {
47
- const res = await fetch(`/api/functions/${ref}`, {
48
- method: 'POST',
49
- headers: { 'Content-Type': 'application/json' },
50
- body: JSON.stringify({ params }),
51
- })
52
- return res.json()
53
- },
44
+ Name, surname, birth date, and a Submit button. No dropdowns, no nested components — just schema, view, and a minimal delegate.
54
45
 
55
- async loadComponent(ref) {
56
- const [schema, view] = await Promise.all([
57
- fetch(`/api/components/${ref}/schema.json`).then(r => r.json()),
58
- fetch(`/api/components/${ref}/view.json`).then(r => r.json()),
59
- ])
60
- return { schema, view }
61
- },
46
+ **Schema:**
47
+ ```json
48
+ {
49
+ "$schema": "https://amorphie.io/meta/view-model-vocabulary",
50
+ "$id": "urn:amorphie:res:schema:demo:quick-start",
51
+ "type": "object",
52
+ "required": ["name", "surname"],
53
+ "properties": {
54
+ "name": { "type": "string", "minLength": 1, "x-labels": { "en": "First Name", "tr": "Ad" } },
55
+ "surname": { "type": "string", "minLength": 1, "x-labels": { "en": "Surname", "tr": "Soyad" } },
56
+ "birthDate": { "type": "string", "format": "date", "x-labels": { "en": "Date of Birth", "tr": "Doğum Tarihi" } }
57
+ }
58
+ }
59
+ ```
62
60
 
63
- async onAction(action, formData, command) {
64
- if (action === 'submit') {
65
- await fetch('/api/submit', {
66
- method: 'POST',
67
- headers: { 'Content-Type': 'application/json' },
68
- body: JSON.stringify(formData),
69
- })
70
- }
61
+ **View:**
62
+ ```json
63
+ {
64
+ "$schema": "https://amorphie.io/meta/view-vocabulary/1.0",
65
+ "dataSchema": "urn:amorphie:res:schema:demo:quick-start",
66
+ "view": {
67
+ "type": "Column",
68
+ "gap": "md",
69
+ "children": [
70
+ { "type": "TextField", "bind": "name" },
71
+ { "type": "TextField", "bind": "surname" },
72
+ { "type": "DatePicker", "bind": "birthDate" },
73
+ { "type": "Button", "label": { "en": "Submit", "tr": "Gönder" }, "variant": "filled", "action": "submit" }
74
+ ]
75
+ }
76
+ }
77
+ ```
78
+
79
+ **Delegate + render:** `requestData` and `loadComponent` are never called here, so we stub them to throw. Only `onAction` matters.
80
+
81
+ ```vue
82
+ <script setup lang="ts">
83
+ import { provideDelegate } from '@burgantech/pseudo-ui/vue'
84
+ import { PseudoView } from '@burgantech/pseudo-ui/vue'
85
+ import '@burgantech/pseudo-ui/vue/style.css'
86
+
87
+ import type { PseudoViewDelegate, DataSchema, ViewDefinition } from '@burgantech/pseudo-ui'
88
+
89
+ const schema: DataSchema = {
90
+ $schema: 'https://amorphie.io/meta/view-model-vocabulary',
91
+ $id: 'urn:amorphie:res:schema:demo:quick-start',
92
+ type: 'object',
93
+ required: ['name', 'surname'],
94
+ properties: {
95
+ name: { type: 'string', minLength: 1, 'x-labels': { en: 'First Name', tr: 'Ad' } },
96
+ surname: { type: 'string', minLength: 1, 'x-labels': { en: 'Surname', tr: 'Soyad' } },
97
+ birthDate: { type: 'string', format: 'date', 'x-labels': { en: 'Date of Birth', tr: 'Doğum Tarihi' } },
71
98
  },
99
+ }
72
100
 
73
- // Optional: custom server-side validation
74
- async onValidationRequest(field, value, formData) {
75
- const res = await fetch(`/api/validate/${field}`, {
76
- method: 'POST',
77
- headers: { 'Content-Type': 'application/json' },
78
- body: JSON.stringify({ value, formData }),
79
- })
80
- const { error } = await res.json()
81
- return error ?? null
101
+ const view: ViewDefinition = {
102
+ $schema: 'https://amorphie.io/meta/view-vocabulary/1.0',
103
+ dataSchema: 'urn:amorphie:res:schema:demo:quick-start',
104
+ view: {
105
+ type: 'Column',
106
+ gap: 'md',
107
+ children: [
108
+ { type: 'TextField', bind: 'name' },
109
+ { type: 'TextField', bind: 'surname' },
110
+ { type: 'DatePicker', bind: 'birthDate' },
111
+ { type: 'Button', label: { en: 'Submit', tr: 'Gönder' }, variant: 'filled', action: 'submit' },
112
+ ],
82
113
  },
114
+ }
83
115
 
84
- // Optional: capture SDK internal logs
85
- onLog(level, message, error, context) {
86
- console[level](`[pseudo-ui] ${message}`, context ?? '')
116
+ const delegate: PseudoViewDelegate = {
117
+ async requestData() { throw new Error('No LOV in this example — add a dropdown to trigger this') },
118
+ async loadComponent() { throw new Error('No nested components — add type: "Component" to trigger this') },
119
+ async onAction(action, formData) {
120
+ if (action === 'submit') console.log('Form submitted:', formData)
87
121
  },
88
122
  }
123
+ </script>
124
+
125
+ <template>
126
+ <PseudoView :schema="schema" :view="view" lang="en" :delegate="delegate" />
127
+ </template>
89
128
  ```
90
129
 
91
- ### 2. Provide the delegate and render
130
+ ---
131
+
132
+ ### Step 2: Add a continent dropdown (nested component + LOV)
133
+
134
+ We extend the form with a **continent** field. The dropdown lives in a nested sub-component and gets its options via `requestData` — so you see both `loadComponent` (nested UI) and `requestData` (LOV data) in action. All data is inline.
135
+
136
+ **Add to main schema:**
137
+ ```json
138
+ "continent": { "type": "string", "x-labels": { "en": "Continent you live in", "tr": "Yaşadığınız kıta" } }
139
+ ```
140
+
141
+ **Add to main view** (between DatePicker and Button):
142
+ ```json
143
+ {
144
+ "type": "Component",
145
+ "ref": "continent-selector",
146
+ "bind": { "continent": "$form.continent" }
147
+ }
148
+ ```
149
+
150
+ **Sub-component schema** (continent-selector — has `x-lov`, so the SDK calls `requestData("get-continents")`):
151
+ ```json
152
+ {
153
+ "$schema": "https://amorphie.io/meta/view-model-vocabulary",
154
+ "$id": "urn:amorphie:res:schema:demo:continent-selector",
155
+ "type": "object",
156
+ "required": ["continent"],
157
+ "properties": {
158
+ "continent": {
159
+ "type": "string",
160
+ "x-labels": { "en": "Continent you live in", "tr": "Yaşadığınız kıta" },
161
+ "x-lov": {
162
+ "source": "get-continents",
163
+ "valueField": "$.response.data.code",
164
+ "displayField": "$.response.data.name"
165
+ }
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ **Sub-component view:**
172
+ ```json
173
+ {
174
+ "$schema": "https://amorphie.io/meta/view-vocabulary/1.0",
175
+ "dataSchema": "urn:amorphie:res:schema:demo:continent-selector",
176
+ "view": { "type": "Dropdown", "bind": "continent" }
177
+ }
178
+ ```
179
+
180
+ **Full delegate** (replace the stubs with real implementations):
92
181
 
93
182
  ```vue
94
183
  <script setup lang="ts">
95
- import { ref, onMounted } from 'vue'
96
184
  import { provideDelegate } from '@burgantech/pseudo-ui/vue'
97
185
  import { PseudoView } from '@burgantech/pseudo-ui/vue'
98
186
  import '@burgantech/pseudo-ui/vue/style.css'
99
187
 
100
- import type { DataSchema, ViewDefinition } from '@burgantech/pseudo-ui'
188
+ import type { PseudoViewDelegate, DataSchema, ViewDefinition } from '@burgantech/pseudo-ui'
189
+
190
+ const CONTINENTS = [
191
+ { code: 'eu', name: 'Europe' },
192
+ { code: 'as', name: 'Asia' },
193
+ { code: 'na', name: 'North America' },
194
+ { code: 'sa', name: 'South America' },
195
+ { code: 'af', name: 'Africa' },
196
+ { code: 'oc', name: 'Oceania' },
197
+ { code: 'an', name: 'Antarctica' },
198
+ ]
199
+
200
+ const mainSchema: DataSchema = {
201
+ $schema: 'https://amorphie.io/meta/view-model-vocabulary',
202
+ $id: 'urn:amorphie:res:schema:demo:quick-start',
203
+ type: 'object',
204
+ required: ['name', 'surname'],
205
+ properties: {
206
+ name: { type: 'string', minLength: 1, 'x-labels': { en: 'First Name', tr: 'Ad' } },
207
+ surname: { type: 'string', minLength: 1, 'x-labels': { en: 'Surname', tr: 'Soyad' } },
208
+ birthDate: { type: 'string', format: 'date', 'x-labels': { en: 'Date of Birth', tr: 'Doğum Tarihi' } },
209
+ continent: { type: 'string', 'x-labels': { en: 'Continent you live in', tr: 'Yaşadığınız kıta' } },
210
+ },
211
+ }
101
212
 
102
- provideDelegate(delegate)
213
+ const continentSchema: DataSchema = {
214
+ $schema: 'https://amorphie.io/meta/view-model-vocabulary',
215
+ $id: 'urn:amorphie:res:schema:demo:continent-selector',
216
+ type: 'object',
217
+ required: ['continent'],
218
+ properties: {
219
+ continent: {
220
+ type: 'string',
221
+ 'x-labels': { en: 'Continent you live in', tr: 'Yaşadığınız kıta' },
222
+ 'x-lov': { source: 'get-continents', valueField: '$.response.data.code', displayField: '$.response.data.name' },
223
+ },
224
+ },
225
+ }
103
226
 
104
- const schema = ref<DataSchema>()
105
- const view = ref<ViewDefinition>()
227
+ const mainView: ViewDefinition = {
228
+ $schema: 'https://amorphie.io/meta/view-vocabulary/1.0',
229
+ dataSchema: 'urn:amorphie:res:schema:demo:quick-start',
230
+ view: {
231
+ type: 'Column',
232
+ gap: 'md',
233
+ children: [
234
+ { type: 'TextField', bind: 'name' },
235
+ { type: 'TextField', bind: 'surname' },
236
+ { type: 'DatePicker', bind: 'birthDate' },
237
+ { type: 'Component', ref: 'continent-selector', bind: { continent: '$form.continent' } },
238
+ { type: 'Button', label: { en: 'Submit', tr: 'Gönder' }, variant: 'filled', action: 'submit' },
239
+ ],
240
+ },
241
+ }
106
242
 
107
- onMounted(async () => {
108
- const [s, v] = await Promise.all([
109
- fetch('/api/components/my-form/schema.json').then(r => r.json()),
110
- fetch('/api/components/my-form/view.json').then(r => r.json()),
111
- ])
112
- schema.value = s
113
- view.value = v
114
- })
243
+ const delegate: PseudoViewDelegate = {
244
+ async requestData(ref, params) {
245
+ if (ref === 'get-continents') return { response: { data: CONTINENTS } }
246
+ throw new Error(`Unknown data source: ${ref}`)
247
+ },
248
+ async loadComponent(ref) {
249
+ if (ref === 'continent-selector') {
250
+ return {
251
+ schema: continentSchema,
252
+ view: { $schema: 'https://amorphie.io/meta/view-vocabulary/1.0', dataSchema: 'urn:amorphie:res:schema:demo:continent-selector', view: { type: 'Dropdown', bind: 'continent' } },
253
+ }
254
+ }
255
+ throw new Error(`Unknown component: ${ref}`)
256
+ },
257
+ async onAction(action, formData) {
258
+ if (action === 'submit') console.log('Form submitted:', formData)
259
+ },
260
+ }
115
261
  </script>
116
262
 
117
263
  <template>
118
- <PseudoView
119
- v-if="schema && view"
120
- :schema="schema"
121
- :view="view"
122
- lang="en"
123
- @form-change="console.log('form data:', $event)"
124
- @validation-change="console.log('is valid:', $event)"
125
- />
264
+ <PseudoView :schema="mainSchema" :view="mainView" lang="en" :delegate="delegate" />
126
265
  </template>
127
266
  ```
128
267
 
268
+ Step 1 gives you the basics. Step 2 shows how to add a nested component and LOV — `requestData` serves dropdown options, `loadComponent` serves the sub-component.
269
+
270
+ ---
271
+
272
+ ### Initial data (optional)
273
+
274
+ You can pass initial values when the view first renders:
275
+
276
+ | Prop | Purpose |
277
+ |---|---|
278
+ | `formData` | Pre-fill editable fields (e.g. user draft, edit mode). Fields are bound to `$form` and can be changed by the user. |
279
+ | `instanceData` | Backend/persisted data (e.g. read-only display, lookup filters). Used by `$instance` expressions and summary views. |
280
+
281
+ ```vue
282
+ <PseudoView
283
+ :schema="schema"
284
+ :view="view"
285
+ :form-data="{ name: 'Jane', surname: 'Doe', birthDate: '1990-05-15' }"
286
+ :instance-data="{ status: 'active', createdAt: '2024-01-01' }"
287
+ lang="en"
288
+ :delegate="delegate"
289
+ />
290
+ ```
291
+
292
+ `formData` is for user-editable data. `instanceData` is for backend state that drives display and lookups — both are optional and merged when the view mounts.
293
+
294
+ ---
295
+
296
+ ### Lookups (enrichment)
297
+
298
+ When a schema property has `x-lookup`, the SDK fetches enrichment data via `requestData`. You must **activate** the lookup by listing it in the view's `lookups` array — otherwise it won't run.
299
+
300
+ **Schema** (defines the lookup):
301
+ ```json
302
+ {
303
+ "branchDetail": {
304
+ "type": "object",
305
+ "x-lookup": {
306
+ "source": "get-branch-details",
307
+ "resultField": "$.response.data",
308
+ "filter": [{ "param": "branchCode", "value": "$param.selectedBranchCode", "required": true }]
309
+ }
310
+ }
311
+ }
312
+ ```
313
+
314
+ **View** (activates it):
315
+ ```json
316
+ {
317
+ "$schema": "https://amorphie.io/meta/view-vocabulary/1.0",
318
+ "dataSchema": "urn:amorphie:res:schema:shared:branch-info",
319
+ "lookups": ["branchDetail"],
320
+ "view": { ... }
321
+ }
322
+ ```
323
+
324
+ Then use `$lookup.branchDetail.address`, `$lookup.branchDetail.phone`, etc. in Text or other components. The SDK calls `requestData(source, filterParams)` when the view mounts; the delegate returns the enrichment payload.
325
+
129
326
  ## Package Exports
130
327
 
131
328
  | Import path | Content |
@@ -176,77 +373,11 @@ onMounted(async () => {
176
373
  | **View** | `view.json` | UI component tree — layout, binding, actions, transient UI state |
177
374
  | **Model** | Backend | Persisted data, served via delegate's `requestData` |
178
375
 
179
- ### Schema example (ViewModel)
180
-
181
- ```json
182
- {
183
- "$id": "urn:amorphie:res:schema:shared:customer-form",
184
- "type": "object",
185
- "required": ["fullName", "city"],
186
- "properties": {
187
- "fullName": {
188
- "type": "string",
189
- "minLength": 2,
190
- "x-labels": { "en": "Full Name", "tr": "Ad Soyad" },
191
- "x-errorMessages": {
192
- "required": { "en": "Required", "tr": "Zorunlu" }
193
- }
194
- },
195
- "city": {
196
- "type": "string",
197
- "x-labels": { "en": "City", "tr": "Şehir" },
198
- "x-lov": {
199
- "source": "urn:amorphie:func:domain:shared:get-cities",
200
- "valueField": "$.response.data.code",
201
- "displayField": "$.response.data.name"
202
- }
203
- },
204
- "companyName": {
205
- "type": "string",
206
- "x-labels": { "en": "Company", "tr": "Şirket" },
207
- "x-conditional": {
208
- "showIf": {
209
- "allOf": [
210
- { "field": "accountType", "operator": "equals", "value": "corporate" },
211
- { "field": "age", "operator": "greaterThan", "value": 17 }
212
- ]
213
- }
214
- }
215
- }
216
- }
217
- }
218
- ```
219
-
220
- ### View example
221
-
222
- ```json
223
- {
224
- "$schema": "https://amorphie.io/meta/view-vocabulary/1.0",
225
- "dataSchema": "urn:amorphie:res:schema:shared:customer-form",
226
- "uiState": { "showDialog": false },
227
- "view": {
228
- "type": "Column",
229
- "gap": "md",
230
- "children": [
231
- { "type": "TextField", "bind": "fullName" },
232
- { "type": "Dropdown", "bind": "city" },
233
- { "type": "TextField", "bind": "companyName" },
234
- {
235
- "type": "Button",
236
- "label": { "en": "Submit", "tr": "Gönder" },
237
- "variant": "filled",
238
- "action": "submit"
239
- }
240
- ]
241
- }
242
- }
243
- ```
244
-
245
376
  ## Delegate Interface
246
377
 
247
378
  ```typescript
248
379
  interface PseudoViewDelegate {
249
- /** Fetch data from backend (LOV items, lookup enrichment) */
380
+ /** Fetch data from backend (LOV items, lookup enrichment). ref = source (required); params = resolved filter from x-lookup (optional when no filter). */
250
381
  requestData(ref: string, params?: Record<string, string>): Promise<unknown>
251
382
 
252
383
  /** Load a nested component's schema + view by reference */
@@ -329,7 +460,7 @@ import viewModelVocab from '@burgantech/pseudo-ui/vocabularies/view-model-vocabu
329
460
 
330
461
  ## Cross-Platform
331
462
 
332
- This package provides the **TypeScript/Vue** implementation. A **Dart/Flutter** package (`pseudo_ui`) renders the same JSON schemas and views using Material 3 widgets. Both packages share the same vocabulary definitions, ensuring consistent behavior across platforms.
463
+ This package provides the **TypeScript/Vue** implementation. A **Dart/Flutter** package is planned and will render the same JSON schemas and views using Material 3 widgets. Both will share the same vocabulary definitions, ensuring consistent behavior across platforms.
333
464
 
334
465
  ## License
335
466
 
@@ -1,7 +1,7 @@
1
1
  import o from "./DynamicRenderer.vue2.js";
2
2
  /* empty css */
3
3
  import r from "../../_virtual/_plugin-vue_export-helper.js";
4
- const a = /* @__PURE__ */ r(o, [["__scopeId", "data-v-c6e3365d"]]);
4
+ const a = /* @__PURE__ */ r(o, [["__scopeId", "data-v-3324b6bc"]]);
5
5
  export {
6
6
  a as default
7
7
  };