{"_id":"@emfts/uimodel-composer","name":"@emfts/uimodel-composer","dist-tags":{"latest":"0.0.2-next.1"},"versions":{"0.0.2-next.1":{"name":"@emfts/uimodel-composer","version":"0.0.2-next.1","type":"module","description":"UIModel-driven Vue 3 composer for EMF.ts models","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./style.css":"./dist/style.css","./css":{"import":"./dist/css/index.js","types":"./dist/css/index.d.ts"}},"scripts":{"generate":"emfts-codegen generate -m model/uimodel.ecore -c model/uimodel.genconfig.xmi","generate:css":"emfts-codegen generate -m model/uimodel-css.ecore -c model/uimodel-css.genconfig.xmi -d model/uimodel.ecore --import-mapping http://uimodel/1.0=..","build":"vue-tsc --noEmit && vite build","type-check":"vue-tsc --noEmit","test":"vitest","test:run":"vitest run"},"dependencies":{"@emfts/core":"^0.1.1-next.16","@emfts/vue-registry":"^0.0.1-next.1"},"devDependencies":{"@emfts/codegen":"^0.0.1-next.7","@types/node":"^20.10.0","@vitejs/plugin-vue":"^5.0.0","typescript":"^5.3.0","vite":"^5.0.0","vite-plugin-dts":"^3.7.0","vitest":"^4.1.4","vue":"^3.4.0","vue-tsc":"^3.2.4"},"peerDependencies":{"vue":"^3.4.0"},"peerDependenciesMeta":{},"keywords":["emf","ecore","vue","uimodel","model-driven","emfts"],"publishConfig":{"access":"public"},"license":"EPL-2.0","repository":{"type":"git","url":"git+https://github.com/eclipse-fennec/emf.ts.ui.git","directory":"packages/uimodel-composer"},"_id":"@emfts/uimodel-composer@0.0.2-next.1","gitHead":"dc8782d049a90e5fb38d2a9a7f5c49faf1389e7e","bugs":{"url":"https://github.com/eclipse-fennec/emf.ts.ui/issues"},"homepage":"https://github.com/eclipse-fennec/emf.ts.ui#readme","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-x3XYRY90Qh+Ythp3Kd84heCoeqMFt3vU64unJlLuIbAUu7+O8W1kzogiiqzlytuu8ArEa22aKLB4iim3cMzgHA==","shasum":"4cb59d6480c2fedf15396cca8be2305bd22ed0b6","tarball":"https://registry.npmjs.org/@emfts/uimodel-composer/-/uimodel-composer-0.0.2-next.1.tgz","fileCount":361,"unpackedSize":568089,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDQDT4nKKbqEwv6REPwoVy3SITAPBWQs8L2aMDqeaB2XAiEA70Snidnc+1nuTQc5MqvwN+yBqA+d7uaijl0nZyF5aAA="}]},"_npmUser":{"name":"markus.hochstein","email":"markus.hochstein@software-hochstein.de"},"directories":{},"maintainers":[{"name":"markus.hochstein","email":"markus.hochstein@software-hochstein.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/uimodel-composer_0.0.2-next.1_1786535114669_0.7838078190512354"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-12T11:45:14.401Z","0.0.2-next.1":"2026-08-12T11:45:14.844Z","modified":"2026-08-12T11:45:15.100Z"},"maintainers":[{"name":"markus.hochstein","email":"markus.hochstein@software-hochstein.de"}],"description":"UIModel-driven Vue 3 composer for EMF.ts models","homepage":"https://github.com/eclipse-fennec/emf.ts.ui#readme","keywords":["emf","ecore","vue","uimodel","model-driven","emfts"],"repository":{"type":"git","url":"git+https://github.com/eclipse-fennec/emf.ts.ui.git","directory":"packages/uimodel-composer"},"bugs":{"url":"https://github.com/eclipse-fennec/emf.ts.ui/issues"},"license":"EPL-2.0","readme":"# @emfts/uimodel-composer\n\nUIModel-getriebener Vue-3-Composer für [EMF.ts](https://github.com/eclipse-fennec/emf.ts)-Modelle.\n\nStatt UIs von Hand zu bauen, wird die Oberfläche als **UIModel** (Ecore/XMI) beschrieben und zur\nLaufzeit interpretiert: Der `UIModelComposer` rendert aus einem geladenen UIModel und einem\nDomänen-`EObject` reaktiv die passende Vue-Komponenten-Hierarchie.\n\n## Features\n\n- **View-Typen**: `FormView`, `SectionView`, `TabView`, `TableView`, `SummaryView`, `MasterDetail`\n- **Widgets**: Input, TextArea, Number, Checkbox, Date, Combobox, Select, ReferenceLink —\n  Rendering über die `@emfts/vue-registry` (eigene Widgets pro EDataType registrierbar)\n- **Styles**: kaskadierende Style-Ketten (`BaseStyle`, `LayoutStyle`, `WidgetStyle`, `TableStyle`)\n  mit `resolveStyleChain`\n- **Expressions**: Sichtbarkeit und Validierung modellgetrieben via JS- oder OCL-Ausdrücken\n  (`registerOclEvaluator` für einen OCL-Adapter, z. B. `@emfts/ocl.langium`)\n- **Validierung**: `ValidationExpression` + `ValidationMessageMapper` mit Severities\n- **CSS-Styling** (`http://uimodel/css/1.0`): modelliertes, CSS-basiertes Styling\n  (Design-Tokens, Themes, benannte Styles, Selektor-Regeln) — Teil dieses Pakets,\n  siehe unten\n- **Erweiterungs-Pakete** (eigene EPackages, verändern das Core-UIModel nicht):\n  [`@emfts/uimodel-vega`](../uimodel-vega) (Charts),\n  [`@emfts/uimodel-maps`](../uimodel-maps) (Karten, QGIS-orientiert),\n  [`@emfts/uimodel-diagram`](../uimodel-diagram) (Diagramme)\n- **Editor**: [`apps/editor`](../../apps/editor) — Baum + Live-Preview + reflektives\n  Property-Panel für UI-Modelle und Styles, mit Undo/Redo und XMI-Speichern\n\n## Installation\n\n```bash\nnpm install @emfts/uimodel-composer vue\n```\n\n`@emfts/core` und `@emfts/vue-registry` kommen als Dependencies mit; `vue` ist\nPeer-Dependency.\n\nDie Renderer für Charts, Karten und Diagramme sind **eigene Pakete** und bringen\nihre Bibliotheken als Peer-Dependencies mit — installiere nur, was du nutzt:\n\n| Erweiterung | Paket | zusätzlich |\n|---|---|---|\n| Vega-Charts | `@emfts/uimodel-vega` | `vega-embed` |\n| Karten (OpenLayers) | `@emfts/uimodel-maps` | `ol` |\n| Diagramme (ELK) | `@emfts/uimodel-diagram` | `elkjs` |\n\nDer Core selbst hängt an keiner dieser Bibliotheken.\n\n## Quick Start\n\n```ts\nimport { createApp } from 'vue';\nimport { EmftsRendererPlugin, componentRegistry } from '@emfts/vue-registry';\nimport { registerEcorePackage, EPackageRegistry } from '@emfts/core';\nimport { UimodelPackage, UimodelFactory } from '@emfts/uimodel-composer';\n\n// 1. Packages registrieren (Ecore-Basis, UIModel, eigene Domänen-Packages)\nregisterEcorePackage();\nconst uimodel = UimodelPackage.eINSTANCE;\nuimodel.setEFactoryInstance(UimodelFactory.eINSTANCE);\nEPackageRegistry.INSTANCE.set(uimodel.getNsURI()!, uimodel);\n\n// 2. UIModel- und Domänen-XMI über ein ResourceSet laden, dann App mounten\nconst app = createApp(App, { uiModel, model });\napp.use(EmftsRendererPlugin, { registry: componentRegistry });\napp.mount('#app');\n```\n\n```vue\n<template>\n  <UIModelComposer :ui-model=\"uiModel\" :model=\"model\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { UIModelComposer, type UIModel } from '@emfts/uimodel-composer';\nimport type { EObject } from '@emfts/core';\n\ndefineProps<{ uiModel: UIModel; model: EObject }>();\n</script>\n```\n\n**Wichtig bei den Erweiterungs-Paketen:** `@emfts/uimodel-vega`,\n`@emfts/uimodel-maps` und das CSS-Sub-Entry erst **nach** der Registrierung des\nCore-UIModels dynamisch importieren — deren Package-Initialisierung liest\n`Component` aus der `EPackageRegistry`. Die Renderer werden der ComposerRegistry\nbeigesteuert:\n\n```ts\nimport { VegaViewComposer } from '@emfts/uimodel-vega';\nconst registry = createComposerRegistry({ ...defaults, VegaView: VegaViewComposer });\n```\n\nEin vollständiges, lauffähiges Setup (XMI-Loading, OCL-Adapter, eigene Widgets) zeigt die\nExample-App unter [`apps/example`](../../apps/example).\n\n## Modell & Codegen\n\nDie TypeScript-Klassen unter `src/generated/` werden **nicht von Hand gepflegt**, sondern mit\n`@emfts/codegen` aus den Ecore-Modellen unter [`model/`](model/) generiert:\n\n```bash\nnpm run generate        # Core-UIModel  (model/uimodel.ecore)\nnpm run generate:css    # CSS-Styling    (model/uimodel-css.ecore)\n\n# Erweiterungs-Pakete generieren aus ihrem eigenen Verzeichnis:\nnpm run generate --workspace packages/uimodel-vega\nnpm run generate --workspace packages/uimodel-maps\n```\n\nÄnderungen am Metamodell immer im `.ecore` vornehmen und anschließend neu generieren.\n\n## AllFeatures — generische Feature-Platzhalter\n\n`AllFeatures extends WidgetComponent` ist ein **Platzhalter in `FormView.fields`**\n([#2](https://github.com/eclipse-fennec/emf.ts.ui/issues/2),\n[#4](https://github.com/eclipse-fennec/emf.ts.ui/issues/4)): der\n`FormViewComposer` expandiert ihn beim Iterieren gegen die EClass des\nDomänenobjekts und rendert die Treffer als normale Widgets — ohne eigenes\nMarkup; Überschriften rendern Konsumenten pro `group` (z. B. über das\ngestempelte `data-uim-group`). Die geerbten Widget-Eigenschaften des\nPlatzhalters (`label`, `readOnly`, `required`, `styles`, `bindings`, …) sind\ndie Default-Konfiguration jedes Treffers:\n\n- **Auswahl**: `with` (explizite Features, definierte Reihenfolge) *oder*\n  Grundmenge (alle Features inkl. geerbte), zugeschnitten durch `eType` und\n  `filter` (UND). `filter` ist ein **Meta-Ausdruck** — `self` ist das\n  `EStructuralFeature`: `self.derived`, `self.iD === true`,\n  `self.eClass().getName() === 'EAttribute'`, `self.name === 'description'`\n  (JS; Getter-Konvention `derived` → `isDerived()` übernimmt der Expression-Proxy).\n- **Konflikte**: `priority` (höher gewinnt) → Spezifität (`with` > gefiltert) →\n  Dokument-Reihenfolge. Jedes Feature landet in höchstens einem Block\n  (Dedup pro UIModel); explizit gebundene Widgets gewinnen immer.\n- **Template**: `template` referenziert einen `WidgetComponent`-Prototyp **ohne**\n  gebundenes `feature` (href in Vorlagen-Dateien möglich). Pro Treffer wird\n  geklont, `feature` gebunden und das Label aus dem Feature-Namen abgeleitet.\n- **Widget-Wahl als Fallliste** (`cases: TemplateCase[*]`,\n  [#5](https://github.com/eclipse-fennec/emf.ts.ui/issues/5)): geordnete\n  Fallunterscheidung — `when` ist ein Meta-Ausdruck (fail-closed), erster\n  Treffer gewinnt, `widget` referenziert einen Prototyp aus\n  `UIModel.templates` (oder per href eine Vorlagen-Datei). `template`\n  bleibt als Kurzform „ein Default-Fall ohne when“ (nach den cases geprüft).\n  Trifft nichts und es gibt keinen Default-Fall, ist das ein\n  **Renderfehler** — ein eingebautes Code-Mapping (`defaultWidgetFor`)\n  existiert nicht mehr; das frühere Mapping steht als explizite Fallliste\n  im mitgelieferten generischen Default-Modell und ist damit lesbar und\n  überschreibbar.\n- **Workspace-Overlays** (`UIModelOverlay`,\n  [#8](https://github.com/eclipse-fennec/emf.ts.ui/issues/8)):\n  Wurzelkonzept ohne Struktur — nur Prototypen-Katalog + `cases`, die\n  die Widget-Wahl der Expansion **punktuell übersteuern** (geprüft vor\n  den lokalen cases; `priority` reiht mehrere Overlays, explizit\n  gebundene Widgets sind nie betroffen). Transport über\n  `ExpansionContext.overlayCases` (`collectOverlayCases(overlays)`),\n  bequem als `<UIModelComposer :overlays=\"[…]\">`.\n- **API**: purer Kern `expandFeatures(eClass, block, context)` +\n  `collectExpansionContext(uiModel)`; Expansion im `FormViewComposer`\n  (der frühere `AllFeaturesComposer` ist entfernt, #7).\n- **required**: wird aus der Multiplizität abgeleitet (`lowerBound >= 1`,\n  explizites `required` am Prototyp/Block gewinnt); dabei wird eine\n  Required-`ValidationExpression` generiert, sofern der Prototyp keine\n  eigenen Validations mitbringt (dokumentierte Konvention, #7).\n\nKanonisches Beispiel: [`model/templates/generic-default.uimodel.xmi`](model/templates/generic-default.uimodel.xmi)\n(ID → Attribute → Referenzen → Derived, klassenunabhängig) — im Editor über die\nUIModel-Auswahl der Vorschau live erlebbar.\n\n## PropertyBinding — expression-fähige Widget-Parameter\n\n`WidgetComponent.bindings: PropertyBinding[*]` bindet Widget-Parameter an\nExpressions ([Issue #3](https://github.com/eclipse-fennec/emf.ts.ui/issues/3)):\n\n- **Auflösung**: Binding-Ergebnis > statischer Wert am Widget > Ableitung\n  (z. B. Label aus dem Feature-Namen). Ausgewertet **reaktiv zur Renderzeit**\n  in `useWidgetConfig`/`WidgetComposer`.\n- **Kontext**: `self` = Domänenobjekt (wie `visibilityCondition`), zusätzlich\n  `feature` (gebundenes `EStructuralFeature`, Meta-Ebene) und `eClass`.\n  JS voll unterstützt (`evaluateValue`), OCL auf `self`-Ausdrücke beschränkt.\n- **Koersion**: boolesche Parameter → `Boolean(result)`, numerische →\n  `Number(result)`, sonst `String(result)`. Sonderfall `property=\"feature\"`:\n  Ergebnis muss ein `EStructuralFeature` sein, `null` ⇒ Widget wird nicht\n  gerendert. Fail-open: `undefined` ⇒ statischer Wert gilt.\n- **Templates**: `UIModel.templates` ist der Katalog für Widget-Prototypen;\n  `AllFeatures.template` referenziert dorthin (oder per href in eine\n  Vorlagen-Datei). Bindings am Prototyp werden pro Treffer mitgeklont.\n- **Geerbte Bindings**: `AllFeatures.bindings` (von `WidgetComponent` geerbt)\n  gilt für **jedes** expandierte Widget — auch ohne Template, das Typ-Mapping\n  bleibt erhalten (widget-/template-eigene Bindings gewinnen bei gleichem\n  `property`). Damit lassen sich Labels generisch aus EAnnotations ziehen:\n\n```xml\n<components xsi:type=\"uimodel:FormView\" name=\"attributes\" group=\"Attributes\">\n  <fields xsi:type=\"uimodel:AllFeatures\" name=\"attribute\">\n    <filter language=\"JS\" body=\"self.eClass().getName() === 'EAttribute' &amp;&amp; !self.derived\"/>\n    <bindings property=\"label\">\n      <expression language=\"JS\"\n          body=\"feature.getEAnnotation('http://uimodel/1.0')?.getDetails()?.get('label') ?? feature.name\"/>\n    </bindings>\n  </fields>\n</components>\n```\n\nDie Expansion setzt außerdem `required` aus der Multiplizität\n(`lowerBound >= 1`), sofern der Prototyp nichts vorgibt.\n\n## Strukturelle Platzhalter — GroupWidget, Conditional, ForEach\n\nDrei weitere `fields`-Platzhalter\n([#6](https://github.com/eclipse-fennec/emf.ts.ui/issues/6)), frei\nkomponierbar; aufgelöst vom puren Kern `resolveStructure` und gerendert\nvom `FieldsRenderer` (kein Markup über das Nötigste hinaus):\n\n- **`GroupWidget`** (`fields` + `layout`): Container-Widget mit\n  `uimodel-group uimodel-group--<layout>`-Klassen (Styling beim\n  Konsumenten/CSS-Modell). Als Prototyp im `templates`-Katalog ergibt es\n  **komplexe Templates** — die ganze Gruppe wird pro Treffer geklont;\n  Nachfahren ohne eigenes `feature` erben das expandierte Feature.\n- **`Conditional`** (`condition` + `then`/`else`): gruppenfähiges if mit\n  else-Zweig; `self` = Domänenobjekt, fail-open wie `visibilityCondition`.\n- **`ForEach`** (`items` + `body`, optional `emptyText`): Iterator über\n  eine Instanz-Collection — `body` wird pro Element gerendert, `self`/\n  `model` ist dort **das Element** (Widget-`feature` referenziert die\n  Element-Klasse; Bindings, Validierung und CSS-condition-Regeln wirken\n  pro Element).\n\n```xml\n<fields xsi:type=\"uimodel:ForEach\" name=\"colleagues\" emptyText=\"Keine Kollegen.\">\n  <items language=\"JS\" body=\"self.company?.employees\"/>\n  <body xsi:type=\"uimodel:GroupWidget\" name=\"row\" layout=\"HORIZONTAL\">\n    <fields xsi:type=\"uimodel:InputWidget\" name=\"firstName\"\n        feature=\"…//Person/firstName\" readOnly=\"true\"/>\n    <fields xsi:type=\"uimodel:InputWidget\" name=\"jobTitle\"\n        feature=\"…//Person/jobTitle\" readOnly=\"true\"/>\n  </body>\n</fields>\n```\n\n## Widget-Extension-Pakete\n\nEigene Widget-Typen (Code-, Markdown-, RichText-Editoren …) gehören **nicht**\nin den uimodel-Kern, sondern in ein eigenes EPackage des Hosts — analog zu\n`uimodel-vega.ecore`/`uimodel-maps.ecore`\n([#9](https://github.com/eclipse-fennec/emf.ts.ui/issues/9)):\n\n1. **EPackage definieren**: eigener `nsURI` (z. B.\n   `http://gene/uimodel/widgets/1.0`), Klassen erweitern\n   `http://uimodel/1.0#//WidgetComponent` per href und ergänzen eigene\n   Attribute (`language`, `preview`, `toolbarItems`, …). Codegen wie bei den\n   anderen Extensions mit `-d model/uimodel.ecore --import-mapping`.\n2. **Registrieren**: das Package **nach** dem Core-UIModel dynamisch\n   importieren und in die `EPackageRegistry` legen (`_init()` liest\n   `WidgetComponent` aus der Registry).\n3. **Renderer beisteuern**: über die `@emfts/vue-registry`; die Komponente\n   liest Konfiguration aus `custom.resolvedStyle`/`custom.rawWidget`.\n\nDie Kern-Mechanik ist EClass-agnostisch: `cloneComponent` instanziiert über\ndie Factory des jeweiligen EPackage, TemplateCase-/Overlay-Matching,\nPropertyBindings und das CSS-Klassen-Stamping arbeiten reflektiv. Extension-\nWidgets brauchen dafür **generierte Impls**, die von den exportierten\nBasisklassen (`ComponentImpl`, `WidgetComponentImpl`) erben —\n`DynamicEObject`s reichen nicht, weil die Expansion Property-Accessors nutzt.\nVerifiziert in [`src/allfeatures/widgetExtension.test.ts`](src/allfeatures/widgetExtension.test.ts)\n(Expansion, TemplateCase, UIModelOverlay, Bindings auf Extension-Attributen).\n\n**Fallback**: Liefert die Registry für ein Feature keine Komponente, rendert\nder Composer ein `FallbackWidget` (Label + Plaintext-Editor auf dem gebundenen\nFeature) und meldet das einmal pro Widget-Klasse per `console.warn` — statt\nstill leer zu bleiben; Daten gehen dabei nicht verloren.\n\n## Live-Reaktivität (Expression-Tick)\n\nEObjects sind keine Vue-Reactive-Sources — Expression-Ergebnisse\n(Bindings, Visibility, Validierung, `Conditional`/`ForEach`,\ncondition-StyleRules) hängen deshalb am globalen **Expression-Tick**\n([#7](https://github.com/eclipse-fennec/emf.ts.ui/issues/7)):\n\n- Der `UIModelComposer` hängt automatisch einen `EContentAdapter` an das\n  übergebene Domänenobjekt (`useModelTick`) — Widget-Edits und\n  programmatische `eSet`-Aufrufe wirken sofort.\n- Für Änderungen, die der Adapter nicht sieht (andere Resources,\n  Struktur-Änderungen am UIModel selbst), rufen Konsumenten\n  `bumpExpressionTick()`; eigene expression-abhängige computeds lesen\n  `trackExpressionTick()`.\n\n## CSS-Styling-Modell (`http://uimodel/css/1.0`)\n\n[`model/uimodel-css.ecore`](model/uimodel-css.ecore) modelliert Styling CSS-nah, aber\nvollständig als EMF-Modell (eigene Resource, z. B. `styles.xmi`):\n\n| Konzept | EClass | CSS-Analogon |\n|---|---|---|\n| `StyleSheet` | Wurzel: tokens, themes, styles, rules | Stylesheet |\n| `DesignToken` | `name`/`value` → `--uic-<name>` auf `:root` | Custom Property |\n| `Theme` + `TokenOverride` | Token-Overrides, Aktivierung via Klasse `uicss-theme-<name>` | Theme-Scope |\n| `CssStyle` (extends `BaseStyle`!) | benannte Klasse `.uic-<name>`, aus `Component.styles` referenzierbar, `extends`-Kaskade | CSS-Klasse |\n| `CssDeclaration` | `property`/`value`/`important`; `token(<name>)` → `var(--uic-<name>)` | Deklaration |\n| `CssState` | HOVER/FOCUS/ACTIVE/DISABLED/READONLY/INVALID/REQUIRED | Pseudo-Klasse |\n| `StyleRule` | Selektor: `targetClass` (± Subtypen), `componentName`, `group`, `condition` (JS/OCL, dynamisch), `priority`, `media` | CSS-Regel |\n\nDie **CSS-Engine** (`generateCss`) übersetzt ein StyleSheet in echtes CSS;\n`useStyleSheetInjection` hält es reaktiv (EContentAdapter) als `<style>`-Element aktuell.\nDie Composer **stempeln** dafür Klassen und data-Attribute auf jede gerenderte Komponente:\n\n```\n.uim-component .uim-c-<EClass … inkl. Supertypen>   → Typ-Selektoren\n[data-uim-eclass|name|group]                        → exakte Selektoren\n.uic-<name>                                         → referenzierte CssStyles\n.uim-s-required|-readonly|…                         → Widget-Zustände\n.uicss-cond-<sheet>-<i>                             → erfüllte condition-Regeln\n```\n\nVerwendung:\n\n```vue\n<UIModelComposer :ui-model=\"uiModel\" :model=\"model\" :style-sheets=\"[styleSheet]\" />\n```\n\nDas CSS-EPackage wird wie die Erweiterungs-Pakete **nach** dem Core-Package\ndynamisch importiert:\n\n```ts\nconst { UimodelCssPackage, UimodelCssFactory } = await import('@emfts/uimodel-composer/css');\n```\n\n## Editor-App\n\n[`apps/editor`](../../apps/editor) ist ein web-basierter Editor für UI-Modelle **und** Styles\n(orientiert an gene): Outline-Baum über beide Resources, Live-Preview mit\nTheme-Umschalter und CSS-Quelltext-Ansicht, reflektives Property-Panel\n(Attribute, Enums, Referenzen mit Kandidaten-Auflösung) und ein\nDeklarations-Editor für `CssStyle`/`StyleRule`/`CssState` mit\nCSS-Property- und `token(…)`-Vorschlägen. Änderungen laufen als Commands\nüber `@emfts/command` (Undo/Redo, Strg+Z/Y), gespeichert wird als\nXMI-Download (Strg+S).\n\n```bash\nnpm install          # im Repo-Root (npm workspaces)\nnpm run build        # alle Pakete bauen\nnpm run dev:editor   # Editor starten\n```\n\n## Example-App\n\n```bash\nnpm install          # im Repo-Root\nnpm run build\nnpm run dev:example\n```\n\nDie Example-App (`apps/example`) demonstriert Formulare, Tabellen, Master-Detail, Vega-Charts und\nOpenLayers-Karten auf Basis eigener Domänen-Modelle (`example/model/`).\n\n## Entwicklung\n\n```bash\nnpm run build        # vue-tsc --noEmit && vite build\nnpm run type-check   # nur Typprüfung\nnpm test             # vitest (watch)\nnpm run test:run     # vitest einmalig\n```\n\n## Lizenz\n\n[EPL-2.0](https://www.eclipse.org/legal/epl-2.0/)\n","readmeFilename":"README.md","_rev":"1-4db262418850927ec7b88fd7466e4185"}