Latest commit

History

1,165 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Check the docs folder to get a basic understanding of the project's architecture

Demo

https://eisgroup.github.io/ui-render/

Supported view names

docs/SUPPORTED-VIEWS.md lists every name a meta.json may use — the view of a node, the value of a render* attribute, and the action names accepted by onClick, onChange and onDone — including the views that are declared as constants but that no resolver case handles. The page is generated from the FIELD constants plus the resolver source, so run npm run docs:views after adding or removing either; a contract test fails while the page and the source disagree.

Supported props on Table, Tooltip and Select / Dropdown

docs/SUPPORTED-PROPS.md documents the prop surface of the three views the semantic-ui-react exit replaces, split by what actually happens to each prop: consumed by our component, stripped before the DOM, forwarded to semantic-ui-react, or — once a view has been reimplemented — deliberately dropped. Table is in-house already and imports nothing; Tooltip and Select/Dropdown still wrap the package, so the page doubles as the parity checklist for the remaining work. It is generated from the component source and the call sites — run npm run docs:props after changing one — and a contract test additionally checks it against the example corpus.

Installation (consumer)

eis-ui-render declares the following peer dependencies. The host application must install them explicitly — they are not bundled. React must remain a single shared instance, while Moment must be supplied by the host because the library build externalizes it.

PackageRequired versionWhy it must be a peer
react^16.14.0 || ^17.0.0 || ^18.0.0A second copy of React in the tree triggers Invalid hook call and breaks Context (forms, providers). pnpm with strict node_modules will not deduplicate copies across non-overlapping ranges.
react-dom^16.14.0 || ^17.0.0 || ^18.0.0Must use the same major version as react so the renderer pair matches.
moment^2.29.4The library externalizes Moment and uses it for date pickers and formatters, so the host must provide a compatible 2.x version.

Install (npm):

npm install eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

Install (pnpm) — note that with auto-install-peers=false (the strict default in some setups) peer dependencies are not installed automatically, so they must be listed explicitly:

pnpm add eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

React 16.14 and 17 hosts remain supported and may keep matching react@^16.14.0 or react@^17.0.0 dependencies while migrating on their own schedule. The library is developed and tested against React 18.3.

If the host project relies on transitive copies of react/react-dom/moment from another package instead of declaring them directly, pnpm in isolated mode will not resolve our peer through them — the application must declare these three packages itself.

Other libraries previously listed as peer dependencies (final-form, final-form-arrays, react-final-form, react-final-form-arrays, prop-types) are now bundled as regular dependencies of eis-ui-render, so the host project does not need to install them.

eis-ui-render is consumed by a bundler — imported as a React component into a host application. Dropping dist/index.js into a page with a <script> tag is not supported: the UMD global lookup never matched React's real global name, so it has never worked.

Styles and assets (consumer)

The library entry deliberately does not inject CSS, so the host loads the stylesheet itself. Both paths below work and resolve to the same rules — dist/static/*.css are one-line @import re-exports, so the bytes ship only once:

import'eis-ui-render/static/all.css'// or 'eis-ui-render/dist/static/all.css'import'eis-ui-render/static/font.css'// icon font — only if the host does not provide its own

Some renderers reference images by absolute URL (<homepage>/static/images/… — flag icons for the language renderer, for example), so those files must also be reachable from the host's web root. Copy the package's static/ folder there as part of the build; it is self-contained:

cp -R node_modules/eis-ui-render/static ./public/

The meta.json contract (consumer)

The package ships the UI declaration contract as a JSON Schema (draft 2020-12) at eis-ui-render/meta.schema.json, so meta.json authors get autocomplete and validation in their editor instead of discovering a typo at render time.

The quickest way in is a pointer inside the file itself — no workspace configuration, and it works in VS Code and the JetBrains IDEs alike:

{
"$schema": "./node_modules/eis-ui-render/meta.schema.json",
"view": "Col",
"items": [{ "view": "Text", "name": "customer.name" }]
}

Or map it once for every meta file, in .vscode/settings.json:

{
"json.schemas": [
{
"fileMatch": ["**/*_meta.json", "**/meta.json"],
"url": "./node_modules/eis-ui-render/meta.schema.json"
}
]
}

The schema is permissive on purpose. Component attributes are forwarded to the underlying React component, so nodes accept properties the schema does not list, and view, render-method, action and normalizer names suggest the built-in vocabulary without rejecting an unlisted string — the renderer accepts those too. What the schema does constrain is the handful of shapes the engine genuinely requires (items/headers/extraItems/extraHeaders must be arrays, name must be a string), each of which is otherwise a render-time crash.

$schema is stripped before rendering, so adding it changes no output.

Dev-mode validation

The same rules run at runtime behind an opt-in prop. It is off by default and walks nothing until asked, so it costs a default host nothing:

<UIRenderdata={data}meta={meta}validateMeta={process.env.NODE_ENV!=='production'}/>

Each problem is reported to console.warn on one line, naming the JSON path of the offending node rather than leaving a stack trace inside a minified bundle:

[ui-render] meta error at "items[3].items[0].name": name must be a string key path, got number …
[ui-render] meta warning at "headers[2].renderCell": unknown render method "double5" …

error means the engine will fail on that node; warning means it will render, but silently degraded — an unknown view becomes a "field does not exist" placeholder, an unknown render* method falls back to plain text. Pass a function instead of true to collect the problems yourself (validateMeta={problems => …}); the reporter never throws into the host application, whatever it finds.

Contract version

meta.json may declare an optional root-level metaVersion ("MAJOR" or "MAJOR.MINOR") to record which contract it was authored against. The current contract version is 1.

  • Absence means "current" — the file targets whatever contract the installed eis-ui-render implements. That is the right choice when meta and library ship together, and it is why every existing meta.json keeps working untouched.
  • MAJOR changes only for a change that would break existing meta; MINOR for additive ones. Declaring a version equal to or below what the library implements is always compatible.
  • The engine ignores the value and strips the field before rendering: declaring it never changes output. Dev-mode validation is the only thing that reads it, and only to report a malformed value, a version newer than the installed library implements, or a metaVersion placed on a nested node, where it means nothing.

There is no negotiation beyond that, deliberately: the field exists so a future contract change can be additive and announced, not so hosts can request a different renderer.

Note the unrelated legacy version attribute seen in older meta files: it is not a contract version (existing files use it both as a producer version at the root and as a node label deeper in the tree), the engine discards it, and new files should use metaVersion.

Renderer configuration (consumer)

Three props configure how values are formatted and how the shell is labelled. They are published to every component the renderer draws, so a nested Table cell honours them exactly like a top-level field:

<UIRenderdata={data}meta={meta}dateFormat="DD/MM/YYYY"currency="EUR"language="fr"/>
PropDefaultEffect
dateFormatMM-DD-YYYYmoment format tokens for every date the renderer displays (an ISO value in a Text node, a render*: "Date" value) and edits (the date picker's display and parsing)
currencyUSDpublished as a CSS class on the renderer's shell (.app.EUR), for currency-specific styling
languageenpublished as a CSS class on the renderer's shell (.app.lang--fr)

Each is merged, not replaced: passing only dateFormat leaves currency and language at their inherited values. currency is notmeta.currencyCode — that one selects the currency symbol the value renderers print, and is declared in meta rather than passed as a prop.

These props used to be accepted and then silently ignored — every date rendered as MM-DD-YYYY whatever was passed. If your application has been passing dateFormat and compensating for it elsewhere, it now takes effect.

Error reporting (consumer)

The renderer catches a failure per node rather than letting one bad declaration blank the page: the failing node is replaced by a one-line diagnostic and everything around it keeps rendering. Pass onError to receive the same diagnostic as a structured report:

<UIRenderdata={data}meta={meta}onError={report=>Sentry.captureException(report.error,{extra: {metaPath: report.path,componentStack: report.errorInfo.componentStack},})}/>
{error,// the thrown valueerrorInfo,// React's {componentStack}path,// JSON path of the node in `meta`, e.g. 'items[3].items[0]' ('' = the root)props,// that node's resolved props: its meta declaration plus what the engine addedmessage,// the one-line diagnostic, also rendered in place of the failed node}

path is the point of the report — a stack trace out of a minified bundle names React internals, while items[3].items[0] names the declaration to go and fix. It is exact for a failure inside the component a node resolved to; for a failure the renderer hits while preparing a node (a malformed items, say) it names the closest enclosing node, which is the most precise position available.

The library logs the report itself as well, so onError adds a channel rather than silencing the console. It never has to be defensive: a reporter that throws is caught, and the render failure is still reported.

Development Installation

The published package declares engines.node >= 18: that is the floor for consuming it, and the shipped bundle needs nothing newer (its most modern syntax is optional chaining, and the packed artifact is verified to server-render on Node 22 and 24). Building this repository is a different matter and uses the version in .nvmrc, which is what CI installs.

  1. Install Node.js, if you haven't already — use the version in .nvmrc (v24).
  2. Navigate to project root folder and install dependencies by running this command in terminal:

npm install

Available Scripts

In the project directory, you can run:

npm run start

Runs the app in the development mode.
Open http://localhost:3001 to view it in Chrome browser, then activate LiveReload extension.

The page will reload if you make edits.
You will also see any lint errors in the console.

Live build mode

  • Install yalc globally npm install -g yalc
  • In your application add a link to the library with yalc add eis-ui-render --link and reinstall dependencies
  • Run npm run yalc-watch to build library and life reload

How to publish the library

  • Bump the package version with npm version patch (or minor, major, or an explicit version). This also synchronizes every tracked data-version attribute.
  • Inspect the package contents with npm pack --dry-run. The prepack lifecycle verifies version synchronization and builds the library automatically.
  • Verify the artifact with npm run test:pack. It enforces the packaging budgets and then packs, extracts and server-renders the tarball in a throwaway consumer that has only the three peer dependencies available. CI runs both gates on every pull request.
  • Login to npm with npm login if needed.
  • Publish the verified version with npm publish. The same prepack checks and build run again immediately before npm creates the published package.

Do not edit the version in package.json manually: use npm version so source metadata, the release commit, and the Git tag stay in sync.

How to publish on GitHub Pages

  • Run npm run build to prepare artifacts
  • Run npm run deploy to upload artifacts to GitHub

About

Recursive UI Rendering with Dynamic React Components

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

1,165 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Check the docs folder to get a basic understanding of the project's architecture

Demo

https://eisgroup.github.io/ui-render/

Supported view names

docs/SUPPORTED-VIEWS.md lists every name a meta.json may use — the view of a node, the value of a render* attribute, and the action names accepted by onClick, onChange and onDone — including the views that are declared as constants but that no resolver case handles. The page is generated from the FIELD constants plus the resolver source, so run npm run docs:views after adding or removing either; a contract test fails while the page and the source disagree.

Supported props on Table, Tooltip and Select / Dropdown

docs/SUPPORTED-PROPS.md documents the prop surface of the three views the semantic-ui-react exit replaces, split by what actually happens to each prop: consumed by our component, stripped before the DOM, forwarded to semantic-ui-react, or — once a view has been reimplemented — deliberately dropped. Table is in-house already and imports nothing; Tooltip and Select/Dropdown still wrap the package, so the page doubles as the parity checklist for the remaining work. It is generated from the component source and the call sites — run npm run docs:props after changing one — and a contract test additionally checks it against the example corpus.

Installation (consumer)

eis-ui-render declares the following peer dependencies. The host application must install them explicitly — they are not bundled. React must remain a single shared instance, while Moment must be supplied by the host because the library build externalizes it.

PackageRequired versionWhy it must be a peer
react^16.14.0 || ^17.0.0 || ^18.0.0A second copy of React in the tree triggers Invalid hook call and breaks Context (forms, providers). pnpm with strict node_modules will not deduplicate copies across non-overlapping ranges.
react-dom^16.14.0 || ^17.0.0 || ^18.0.0Must use the same major version as react so the renderer pair matches.
moment^2.29.4The library externalizes Moment and uses it for date pickers and formatters, so the host must provide a compatible 2.x version.

Install (npm):

npm install eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

Install (pnpm) — note that with auto-install-peers=false (the strict default in some setups) peer dependencies are not installed automatically, so they must be listed explicitly:

pnpm add eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

React 16.14 and 17 hosts remain supported and may keep matching react@^16.14.0 or react@^17.0.0 dependencies while migrating on their own schedule. The library is developed and tested against React 18.3.

If the host project relies on transitive copies of react/react-dom/moment from another package instead of declaring them directly, pnpm in isolated mode will not resolve our peer through them — the application must declare these three packages itself.

Other libraries previously listed as peer dependencies (final-form, final-form-arrays, react-final-form, react-final-form-arrays, prop-types) are now bundled as regular dependencies of eis-ui-render, so the host project does not need to install them.

eis-ui-render is consumed by a bundler — imported as a React component into a host application. Dropping dist/index.js into a page with a <script> tag is not supported: the UMD global lookup never matched React's real global name, so it has never worked.

Styles and assets (consumer)

The library entry deliberately does not inject CSS, so the host loads the stylesheet itself. Both paths below work and resolve to the same rules — dist/static/*.css are one-line @import re-exports, so the bytes ship only once:

import'eis-ui-render/static/all.css'// or 'eis-ui-render/dist/static/all.css'import'eis-ui-render/static/font.css'// icon font — only if the host does not provide its own

Some renderers reference images by absolute URL (<homepage>/static/images/… — flag icons for the language renderer, for example), so those files must also be reachable from the host's web root. Copy the package's static/ folder there as part of the build; it is self-contained:

cp -R node_modules/eis-ui-render/static ./public/

The meta.json contract (consumer)

The package ships the UI declaration contract as a JSON Schema (draft 2020-12) at eis-ui-render/meta.schema.json, so meta.json authors get autocomplete and validation in their editor instead of discovering a typo at render time.

The quickest way in is a pointer inside the file itself — no workspace configuration, and it works in VS Code and the JetBrains IDEs alike:

{
"$schema": "./node_modules/eis-ui-render/meta.schema.json",
"view": "Col",
"items": [{ "view": "Text", "name": "customer.name" }]
}

Or map it once for every meta file, in .vscode/settings.json:

{
"json.schemas": [
{
"fileMatch": ["**/*_meta.json", "**/meta.json"],
"url": "./node_modules/eis-ui-render/meta.schema.json"
}
]
}

The schema is permissive on purpose. Component attributes are forwarded to the underlying React component, so nodes accept properties the schema does not list, and view, render-method, action and normalizer names suggest the built-in vocabulary without rejecting an unlisted string — the renderer accepts those too. What the schema does constrain is the handful of shapes the engine genuinely requires (items/headers/extraItems/extraHeaders must be arrays, name must be a string), each of which is otherwise a render-time crash.

$schema is stripped before rendering, so adding it changes no output.

Dev-mode validation

The same rules run at runtime behind an opt-in prop. It is off by default and walks nothing until asked, so it costs a default host nothing:

<UIRenderdata={data}meta={meta}validateMeta={process.env.NODE_ENV!=='production'}/>

Each problem is reported to console.warn on one line, naming the JSON path of the offending node rather than leaving a stack trace inside a minified bundle:

[ui-render] meta error at "items[3].items[0].name": name must be a string key path, got number …
[ui-render] meta warning at "headers[2].renderCell": unknown render method "double5" …

error means the engine will fail on that node; warning means it will render, but silently degraded — an unknown view becomes a "field does not exist" placeholder, an unknown render* method falls back to plain text. Pass a function instead of true to collect the problems yourself (validateMeta={problems => …}); the reporter never throws into the host application, whatever it finds.

Contract version

meta.json may declare an optional root-level metaVersion ("MAJOR" or "MAJOR.MINOR") to record which contract it was authored against. The current contract version is 1.

  • Absence means "current" — the file targets whatever contract the installed eis-ui-render implements. That is the right choice when meta and library ship together, and it is why every existing meta.json keeps working untouched.
  • MAJOR changes only for a change that would break existing meta; MINOR for additive ones. Declaring a version equal to or below what the library implements is always compatible.
  • The engine ignores the value and strips the field before rendering: declaring it never changes output. Dev-mode validation is the only thing that reads it, and only to report a malformed value, a version newer than the installed library implements, or a metaVersion placed on a nested node, where it means nothing.

There is no negotiation beyond that, deliberately: the field exists so a future contract change can be additive and announced, not so hosts can request a different renderer.

Note the unrelated legacy version attribute seen in older meta files: it is not a contract version (existing files use it both as a producer version at the root and as a node label deeper in the tree), the engine discards it, and new files should use metaVersion.

Renderer configuration (consumer)

Three props configure how values are formatted and how the shell is labelled. They are published to every component the renderer draws, so a nested Table cell honours them exactly like a top-level field:

<UIRenderdata={data}meta={meta}dateFormat="DD/MM/YYYY"currency="EUR"language="fr"/>
PropDefaultEffect
dateFormatMM-DD-YYYYmoment format tokens for every date the renderer displays (an ISO value in a Text node, a render*: "Date" value) and edits (the date picker's display and parsing)
currencyUSDpublished as a CSS class on the renderer's shell (.app.EUR), for currency-specific styling
languageenpublished as a CSS class on the renderer's shell (.app.lang--fr)

Each is merged, not replaced: passing only dateFormat leaves currency and language at their inherited values. currency is notmeta.currencyCode — that one selects the currency symbol the value renderers print, and is declared in meta rather than passed as a prop.

These props used to be accepted and then silently ignored — every date rendered as MM-DD-YYYY whatever was passed. If your application has been passing dateFormat and compensating for it elsewhere, it now takes effect.

Error reporting (consumer)

The renderer catches a failure per node rather than letting one bad declaration blank the page: the failing node is replaced by a one-line diagnostic and everything around it keeps rendering. Pass onError to receive the same diagnostic as a structured report:

<UIRenderdata={data}meta={meta}onError={report=>Sentry.captureException(report.error,{extra: {metaPath: report.path,componentStack: report.errorInfo.componentStack},})}/>
{error,// the thrown valueerrorInfo,// React's {componentStack}path,// JSON path of the node in `meta`, e.g. 'items[3].items[0]' ('' = the root)props,// that node's resolved props: its meta declaration plus what the engine addedmessage,// the one-line diagnostic, also rendered in place of the failed node}

path is the point of the report — a stack trace out of a minified bundle names React internals, while items[3].items[0] names the declaration to go and fix. It is exact for a failure inside the component a node resolved to; for a failure the renderer hits while preparing a node (a malformed items, say) it names the closest enclosing node, which is the most precise position available.

The library logs the report itself as well, so onError adds a channel rather than silencing the console. It never has to be defensive: a reporter that throws is caught, and the render failure is still reported.

Development Installation

The published package declares engines.node >= 18: that is the floor for consuming it, and the shipped bundle needs nothing newer (its most modern syntax is optional chaining, and the packed artifact is verified to server-render on Node 22 and 24). Building this repository is a different matter and uses the version in .nvmrc, which is what CI installs.

  1. Install Node.js, if you haven't already — use the version in .nvmrc (v24).
  2. Navigate to project root folder and install dependencies by running this command in terminal:

npm install

Available Scripts

In the project directory, you can run:

npm run start

Runs the app in the development mode.
Open http://localhost:3001 to view it in Chrome browser, then activate LiveReload extension.

The page will reload if you make edits.
You will also see any lint errors in the console.

Live build mode

  • Install yalc globally npm install -g yalc
  • In your application add a link to the library with yalc add eis-ui-render --link and reinstall dependencies
  • Run npm run yalc-watch to build library and life reload

How to publish the library

  • Bump the package version with npm version patch (or minor, major, or an explicit version). This also synchronizes every tracked data-version attribute.
  • Inspect the package contents with npm pack --dry-run. The prepack lifecycle verifies version synchronization and builds the library automatically.
  • Verify the artifact with npm run test:pack. It enforces the packaging budgets and then packs, extracts and server-renders the tarball in a throwaway consumer that has only the three peer dependencies available. CI runs both gates on every pull request.
  • Login to npm with npm login if needed.
  • Publish the verified version with npm publish. The same prepack checks and build run again immediately before npm creates the published package.

Do not edit the version in package.json manually: use npm version so source metadata, the release commit, and the Git tag stay in sync.

How to publish on GitHub Pages

  • Run npm run build to prepare artifacts
  • Run npm run deploy to upload artifacts to GitHub

About

Recursive UI Rendering with Dynamic React Components

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,165 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Check the docs folder to get a basic understanding of the project's architecture

Demo

https://eisgroup.github.io/ui-render/

Supported view names

docs/SUPPORTED-VIEWS.md lists every name a meta.json may use — the view of a node, the value of a render* attribute, and the action names accepted by onClick, onChange and onDone — including the views that are declared as constants but that no resolver case handles. The page is generated from the FIELD constants plus the resolver source, so run npm run docs:views after adding or removing either; a contract test fails while the page and the source disagree.

Supported props on Table, Tooltip and Select / Dropdown

docs/SUPPORTED-PROPS.md documents the prop surface of the three views the semantic-ui-react exit replaces, split by what actually happens to each prop: consumed by our component, stripped before the DOM, forwarded to semantic-ui-react, or — once a view has been reimplemented — deliberately dropped. Table is in-house already and imports nothing; Tooltip and Select/Dropdown still wrap the package, so the page doubles as the parity checklist for the remaining work. It is generated from the component source and the call sites — run npm run docs:props after changing one — and a contract test additionally checks it against the example corpus.

Installation (consumer)

eis-ui-render declares the following peer dependencies. The host application must install them explicitly — they are not bundled. React must remain a single shared instance, while Moment must be supplied by the host because the library build externalizes it.

PackageRequired versionWhy it must be a peer
react^16.14.0 || ^17.0.0 || ^18.0.0A second copy of React in the tree triggers Invalid hook call and breaks Context (forms, providers). pnpm with strict node_modules will not deduplicate copies across non-overlapping ranges.
react-dom^16.14.0 || ^17.0.0 || ^18.0.0Must use the same major version as react so the renderer pair matches.
moment^2.29.4The library externalizes Moment and uses it for date pickers and formatters, so the host must provide a compatible 2.x version.

Install (npm):

npm install eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

Install (pnpm) — note that with auto-install-peers=false (the strict default in some setups) peer dependencies are not installed automatically, so they must be listed explicitly:

pnpm add eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

React 16.14 and 17 hosts remain supported and may keep matching react@^16.14.0 or react@^17.0.0 dependencies while migrating on their own schedule. The library is developed and tested against React 18.3.

If the host project relies on transitive copies of react/react-dom/moment from another package instead of declaring them directly, pnpm in isolated mode will not resolve our peer through them — the application must declare these three packages itself.

Other libraries previously listed as peer dependencies (final-form, final-form-arrays, react-final-form, react-final-form-arrays, prop-types) are now bundled as regular dependencies of eis-ui-render, so the host project does not need to install them.

eis-ui-render is consumed by a bundler — imported as a React component into a host application. Dropping dist/index.js into a page with a <script> tag is not supported: the UMD global lookup never matched React's real global name, so it has never worked.

Styles and assets (consumer)

The library entry deliberately does not inject CSS, so the host loads the stylesheet itself. Both paths below work and resolve to the same rules — dist/static/*.css are one-line @import re-exports, so the bytes ship only once:

import'eis-ui-render/static/all.css'// or 'eis-ui-render/dist/static/all.css'import'eis-ui-render/static/font.css'// icon font — only if the host does not provide its own

Some renderers reference images by absolute URL (<homepage>/static/images/… — flag icons for the language renderer, for example), so those files must also be reachable from the host's web root. Copy the package's static/ folder there as part of the build; it is self-contained:

cp -R node_modules/eis-ui-render/static ./public/

The meta.json contract (consumer)

The package ships the UI declaration contract as a JSON Schema (draft 2020-12) at eis-ui-render/meta.schema.json, so meta.json authors get autocomplete and validation in their editor instead of discovering a typo at render time.

The quickest way in is a pointer inside the file itself — no workspace configuration, and it works in VS Code and the JetBrains IDEs alike:

{
"$schema": "./node_modules/eis-ui-render/meta.schema.json",
"view": "Col",
"items": [{ "view": "Text", "name": "customer.name" }]
}

Or map it once for every meta file, in .vscode/settings.json:

{
"json.schemas": [
{
"fileMatch": ["**/*_meta.json", "**/meta.json"],
"url": "./node_modules/eis-ui-render/meta.schema.json"
}
]
}

The schema is permissive on purpose. Component attributes are forwarded to the underlying React component, so nodes accept properties the schema does not list, and view, render-method, action and normalizer names suggest the built-in vocabulary without rejecting an unlisted string — the renderer accepts those too. What the schema does constrain is the handful of shapes the engine genuinely requires (items/headers/extraItems/extraHeaders must be arrays, name must be a string), each of which is otherwise a render-time crash.

$schema is stripped before rendering, so adding it changes no output.

Dev-mode validation

The same rules run at runtime behind an opt-in prop. It is off by default and walks nothing until asked, so it costs a default host nothing:

<UIRenderdata={data}meta={meta}validateMeta={process.env.NODE_ENV!=='production'}/>

Each problem is reported to console.warn on one line, naming the JSON path of the offending node rather than leaving a stack trace inside a minified bundle:

[ui-render] meta error at "items[3].items[0].name": name must be a string key path, got number …
[ui-render] meta warning at "headers[2].renderCell": unknown render method "double5" …

error means the engine will fail on that node; warning means it will render, but silently degraded — an unknown view becomes a "field does not exist" placeholder, an unknown render* method falls back to plain text. Pass a function instead of true to collect the problems yourself (validateMeta={problems => …}); the reporter never throws into the host application, whatever it finds.

Contract version

meta.json may declare an optional root-level metaVersion ("MAJOR" or "MAJOR.MINOR") to record which contract it was authored against. The current contract version is 1.

  • Absence means "current" — the file targets whatever contract the installed eis-ui-render implements. That is the right choice when meta and library ship together, and it is why every existing meta.json keeps working untouched.
  • MAJOR changes only for a change that would break existing meta; MINOR for additive ones. Declaring a version equal to or below what the library implements is always compatible.
  • The engine ignores the value and strips the field before rendering: declaring it never changes output. Dev-mode validation is the only thing that reads it, and only to report a malformed value, a version newer than the installed library implements, or a metaVersion placed on a nested node, where it means nothing.

There is no negotiation beyond that, deliberately: the field exists so a future contract change can be additive and announced, not so hosts can request a different renderer.

Note the unrelated legacy version attribute seen in older meta files: it is not a contract version (existing files use it both as a producer version at the root and as a node label deeper in the tree), the engine discards it, and new files should use metaVersion.

Renderer configuration (consumer)

Three props configure how values are formatted and how the shell is labelled. They are published to every component the renderer draws, so a nested Table cell honours them exactly like a top-level field:

<UIRenderdata={data}meta={meta}dateFormat="DD/MM/YYYY"currency="EUR"language="fr"/>
PropDefaultEffect
dateFormatMM-DD-YYYYmoment format tokens for every date the renderer displays (an ISO value in a Text node, a render*: "Date" value) and edits (the date picker's display and parsing)
currencyUSDpublished as a CSS class on the renderer's shell (.app.EUR), for currency-specific styling
languageenpublished as a CSS class on the renderer's shell (.app.lang--fr)

Each is merged, not replaced: passing only dateFormat leaves currency and language at their inherited values. currency is notmeta.currencyCode — that one selects the currency symbol the value renderers print, and is declared in meta rather than passed as a prop.

These props used to be accepted and then silently ignored — every date rendered as MM-DD-YYYY whatever was passed. If your application has been passing dateFormat and compensating for it elsewhere, it now takes effect.

Error reporting (consumer)

The renderer catches a failure per node rather than letting one bad declaration blank the page: the failing node is replaced by a one-line diagnostic and everything around it keeps rendering. Pass onError to receive the same diagnostic as a structured report:

<UIRenderdata={data}meta={meta}onError={report=>Sentry.captureException(report.error,{extra: {metaPath: report.path,componentStack: report.errorInfo.componentStack},})}/>
{error,// the thrown valueerrorInfo,// React's {componentStack}path,// JSON path of the node in `meta`, e.g. 'items[3].items[0]' ('' = the root)props,// that node's resolved props: its meta declaration plus what the engine addedmessage,// the one-line diagnostic, also rendered in place of the failed node}

path is the point of the report — a stack trace out of a minified bundle names React internals, while items[3].items[0] names the declaration to go and fix. It is exact for a failure inside the component a node resolved to; for a failure the renderer hits while preparing a node (a malformed items, say) it names the closest enclosing node, which is the most precise position available.

The library logs the report itself as well, so onError adds a channel rather than silencing the console. It never has to be defensive: a reporter that throws is caught, and the render failure is still reported.

Development Installation

The published package declares engines.node >= 18: that is the floor for consuming it, and the shipped bundle needs nothing newer (its most modern syntax is optional chaining, and the packed artifact is verified to server-render on Node 22 and 24). Building this repository is a different matter and uses the version in .nvmrc, which is what CI installs.

  1. Install Node.js, if you haven't already — use the version in .nvmrc (v24).
  2. Navigate to project root folder and install dependencies by running this command in terminal:

npm install

Available Scripts

In the project directory, you can run:

npm run start

Runs the app in the development mode.
Open http://localhost:3001 to view it in Chrome browser, then activate LiveReload extension.

The page will reload if you make edits.
You will also see any lint errors in the console.

Live build mode

  • Install yalc globally npm install -g yalc
  • In your application add a link to the library with yalc add eis-ui-render --link and reinstall dependencies
  • Run npm run yalc-watch to build library and life reload

How to publish the library

  • Bump the package version with npm version patch (or minor, major, or an explicit version). This also synchronizes every tracked data-version attribute.
  • Inspect the package contents with npm pack --dry-run. The prepack lifecycle verifies version synchronization and builds the library automatically.
  • Verify the artifact with npm run test:pack. It enforces the packaging budgets and then packs, extracts and server-renders the tarball in a throwaway consumer that has only the three peer dependencies available. CI runs both gates on every pull request.
  • Login to npm with npm login if needed.
  • Publish the verified version with npm publish. The same prepack checks and build run again immediately before npm creates the published package.

Do not edit the version in package.json manually: use npm version so source metadata, the release commit, and the Git tag stay in sync.

How to publish on GitHub Pages

  • Run npm run build to prepare artifacts
  • Run npm run deploy to upload artifacts to GitHub

About

Recursive UI Rendering with Dynamic React Components

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,165 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Check the docs folder to get a basic understanding of the project's architecture

Demo

https://eisgroup.github.io/ui-render/

Supported view names

docs/SUPPORTED-VIEWS.md lists every name a meta.json may use — the view of a node, the value of a render* attribute, and the action names accepted by onClick, onChange and onDone — including the views that are declared as constants but that no resolver case handles. The page is generated from the FIELD constants plus the resolver source, so run npm run docs:views after adding or removing either; a contract test fails while the page and the source disagree.

Supported props on Table, Tooltip and Select / Dropdown

docs/SUPPORTED-PROPS.md documents the prop surface of the three views the semantic-ui-react exit replaces, split by what actually happens to each prop: consumed by our component, stripped before the DOM, forwarded to semantic-ui-react, or — once a view has been reimplemented — deliberately dropped. Table is in-house already and imports nothing; Tooltip and Select/Dropdown still wrap the package, so the page doubles as the parity checklist for the remaining work. It is generated from the component source and the call sites — run npm run docs:props after changing one — and a contract test additionally checks it against the example corpus.

Installation (consumer)

eis-ui-render declares the following peer dependencies. The host application must install them explicitly — they are not bundled. React must remain a single shared instance, while Moment must be supplied by the host because the library build externalizes it.

PackageRequired versionWhy it must be a peer
react^16.14.0 || ^17.0.0 || ^18.0.0A second copy of React in the tree triggers Invalid hook call and breaks Context (forms, providers). pnpm with strict node_modules will not deduplicate copies across non-overlapping ranges.
react-dom^16.14.0 || ^17.0.0 || ^18.0.0Must use the same major version as react so the renderer pair matches.
moment^2.29.4The library externalizes Moment and uses it for date pickers and formatters, so the host must provide a compatible 2.x version.

Install (npm):

npm install eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

Install (pnpm) — note that with auto-install-peers=false (the strict default in some setups) peer dependencies are not installed automatically, so they must be listed explicitly:

pnpm add eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

React 16.14 and 17 hosts remain supported and may keep matching react@^16.14.0 or react@^17.0.0 dependencies while migrating on their own schedule. The library is developed and tested against React 18.3.

If the host project relies on transitive copies of react/react-dom/moment from another package instead of declaring them directly, pnpm in isolated mode will not resolve our peer through them — the application must declare these three packages itself.

Other libraries previously listed as peer dependencies (final-form, final-form-arrays, react-final-form, react-final-form-arrays, prop-types) are now bundled as regular dependencies of eis-ui-render, so the host project does not need to install them.

eis-ui-render is consumed by a bundler — imported as a React component into a host application. Dropping dist/index.js into a page with a <script> tag is not supported: the UMD global lookup never matched React's real global name, so it has never worked.

Styles and assets (consumer)

The library entry deliberately does not inject CSS, so the host loads the stylesheet itself. Both paths below work and resolve to the same rules — dist/static/*.css are one-line @import re-exports, so the bytes ship only once:

import'eis-ui-render/static/all.css'// or 'eis-ui-render/dist/static/all.css'import'eis-ui-render/static/font.css'// icon font — only if the host does not provide its own

Some renderers reference images by absolute URL (<homepage>/static/images/… — flag icons for the language renderer, for example), so those files must also be reachable from the host's web root. Copy the package's static/ folder there as part of the build; it is self-contained:

cp -R node_modules/eis-ui-render/static ./public/

The meta.json contract (consumer)

The package ships the UI declaration contract as a JSON Schema (draft 2020-12) at eis-ui-render/meta.schema.json, so meta.json authors get autocomplete and validation in their editor instead of discovering a typo at render time.

The quickest way in is a pointer inside the file itself — no workspace configuration, and it works in VS Code and the JetBrains IDEs alike:

{
"$schema": "./node_modules/eis-ui-render/meta.schema.json",
"view": "Col",
"items": [{ "view": "Text", "name": "customer.name" }]
}

Or map it once for every meta file, in .vscode/settings.json:

{
"json.schemas": [
{
"fileMatch": ["**/*_meta.json", "**/meta.json"],
"url": "./node_modules/eis-ui-render/meta.schema.json"
}
]
}

The schema is permissive on purpose. Component attributes are forwarded to the underlying React component, so nodes accept properties the schema does not list, and view, render-method, action and normalizer names suggest the built-in vocabulary without rejecting an unlisted string — the renderer accepts those too. What the schema does constrain is the handful of shapes the engine genuinely requires (items/headers/extraItems/extraHeaders must be arrays, name must be a string), each of which is otherwise a render-time crash.

$schema is stripped before rendering, so adding it changes no output.

Dev-mode validation

The same rules run at runtime behind an opt-in prop. It is off by default and walks nothing until asked, so it costs a default host nothing:

<UIRenderdata={data}meta={meta}validateMeta={process.env.NODE_ENV!=='production'}/>

Each problem is reported to console.warn on one line, naming the JSON path of the offending node rather than leaving a stack trace inside a minified bundle:

[ui-render] meta error at "items[3].items[0].name": name must be a string key path, got number …
[ui-render] meta warning at "headers[2].renderCell": unknown render method "double5" …

error means the engine will fail on that node; warning means it will render, but silently degraded — an unknown view becomes a "field does not exist" placeholder, an unknown render* method falls back to plain text. Pass a function instead of true to collect the problems yourself (validateMeta={problems => …}); the reporter never throws into the host application, whatever it finds.

Contract version

meta.json may declare an optional root-level metaVersion ("MAJOR" or "MAJOR.MINOR") to record which contract it was authored against. The current contract version is 1.

  • Absence means "current" — the file targets whatever contract the installed eis-ui-render implements. That is the right choice when meta and library ship together, and it is why every existing meta.json keeps working untouched.
  • MAJOR changes only for a change that would break existing meta; MINOR for additive ones. Declaring a version equal to or below what the library implements is always compatible.
  • The engine ignores the value and strips the field before rendering: declaring it never changes output. Dev-mode validation is the only thing that reads it, and only to report a malformed value, a version newer than the installed library implements, or a metaVersion placed on a nested node, where it means nothing.

There is no negotiation beyond that, deliberately: the field exists so a future contract change can be additive and announced, not so hosts can request a different renderer.

Note the unrelated legacy version attribute seen in older meta files: it is not a contract version (existing files use it both as a producer version at the root and as a node label deeper in the tree), the engine discards it, and new files should use metaVersion.

Renderer configuration (consumer)

Three props configure how values are formatted and how the shell is labelled. They are published to every component the renderer draws, so a nested Table cell honours them exactly like a top-level field:

<UIRenderdata={data}meta={meta}dateFormat="DD/MM/YYYY"currency="EUR"language="fr"/>
PropDefaultEffect
dateFormatMM-DD-YYYYmoment format tokens for every date the renderer displays (an ISO value in a Text node, a render*: "Date" value) and edits (the date picker's display and parsing)
currencyUSDpublished as a CSS class on the renderer's shell (.app.EUR), for currency-specific styling
languageenpublished as a CSS class on the renderer's shell (.app.lang--fr)

Each is merged, not replaced: passing only dateFormat leaves currency and language at their inherited values. currency is notmeta.currencyCode — that one selects the currency symbol the value renderers print, and is declared in meta rather than passed as a prop.

These props used to be accepted and then silently ignored — every date rendered as MM-DD-YYYY whatever was passed. If your application has been passing dateFormat and compensating for it elsewhere, it now takes effect.

Error reporting (consumer)

The renderer catches a failure per node rather than letting one bad declaration blank the page: the failing node is replaced by a one-line diagnostic and everything around it keeps rendering. Pass onError to receive the same diagnostic as a structured report:

<UIRenderdata={data}meta={meta}onError={report=>Sentry.captureException(report.error,{extra: {metaPath: report.path,componentStack: report.errorInfo.componentStack},})}/>
{error,// the thrown valueerrorInfo,// React's {componentStack}path,// JSON path of the node in `meta`, e.g. 'items[3].items[0]' ('' = the root)props,// that node's resolved props: its meta declaration plus what the engine addedmessage,// the one-line diagnostic, also rendered in place of the failed node}

path is the point of the report — a stack trace out of a minified bundle names React internals, while items[3].items[0] names the declaration to go and fix. It is exact for a failure inside the component a node resolved to; for a failure the renderer hits while preparing a node (a malformed items, say) it names the closest enclosing node, which is the most precise position available.

The library logs the report itself as well, so onError adds a channel rather than silencing the console. It never has to be defensive: a reporter that throws is caught, and the render failure is still reported.

Development Installation

The published package declares engines.node >= 18: that is the floor for consuming it, and the shipped bundle needs nothing newer (its most modern syntax is optional chaining, and the packed artifact is verified to server-render on Node 22 and 24). Building this repository is a different matter and uses the version in .nvmrc, which is what CI installs.

  1. Install Node.js, if you haven't already — use the version in .nvmrc (v24).
  2. Navigate to project root folder and install dependencies by running this command in terminal:

npm install

Available Scripts

In the project directory, you can run:

npm run start

Runs the app in the development mode.
Open http://localhost:3001 to view it in Chrome browser, then activate LiveReload extension.

The page will reload if you make edits.
You will also see any lint errors in the console.

Live build mode

  • Install yalc globally npm install -g yalc
  • In your application add a link to the library with yalc add eis-ui-render --link and reinstall dependencies
  • Run npm run yalc-watch to build library and life reload

How to publish the library

  • Bump the package version with npm version patch (or minor, major, or an explicit version). This also synchronizes every tracked data-version attribute.
  • Inspect the package contents with npm pack --dry-run. The prepack lifecycle verifies version synchronization and builds the library automatically.
  • Verify the artifact with npm run test:pack. It enforces the packaging budgets and then packs, extracts and server-renders the tarball in a throwaway consumer that has only the three peer dependencies available. CI runs both gates on every pull request.
  • Login to npm with npm login if needed.
  • Publish the verified version with npm publish. The same prepack checks and build run again immediately before npm creates the published package.

Do not edit the version in package.json manually: use npm version so source metadata, the release commit, and the Git tag stay in sync.

How to publish on GitHub Pages

  • Run npm run build to prepare artifacts
  • Run npm run deploy to upload artifacts to GitHub

About

Recursive UI Rendering with Dynamic React Components

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

1,165 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Check the docs folder to get a basic understanding of the project's architecture

Demo

https://eisgroup.github.io/ui-render/

Supported view names

docs/SUPPORTED-VIEWS.md lists every name a meta.json may use — the view of a node, the value of a render* attribute, and the action names accepted by onClick, onChange and onDone — including the views that are declared as constants but that no resolver case handles. The page is generated from the FIELD constants plus the resolver source, so run npm run docs:views after adding or removing either; a contract test fails while the page and the source disagree.

Supported props on Table, Tooltip and Select / Dropdown

docs/SUPPORTED-PROPS.md documents the prop surface of the three views the semantic-ui-react exit replaces, split by what actually happens to each prop: consumed by our component, stripped before the DOM, forwarded to semantic-ui-react, or — once a view has been reimplemented — deliberately dropped. Table is in-house already and imports nothing; Tooltip and Select/Dropdown still wrap the package, so the page doubles as the parity checklist for the remaining work. It is generated from the component source and the call sites — run npm run docs:props after changing one — and a contract test additionally checks it against the example corpus.

Installation (consumer)

eis-ui-render declares the following peer dependencies. The host application must install them explicitly — they are not bundled. React must remain a single shared instance, while Moment must be supplied by the host because the library build externalizes it.

PackageRequired versionWhy it must be a peer
react^16.14.0 || ^17.0.0 || ^18.0.0A second copy of React in the tree triggers Invalid hook call and breaks Context (forms, providers). pnpm with strict node_modules will not deduplicate copies across non-overlapping ranges.
react-dom^16.14.0 || ^17.0.0 || ^18.0.0Must use the same major version as react so the renderer pair matches.
moment^2.29.4The library externalizes Moment and uses it for date pickers and formatters, so the host must provide a compatible 2.x version.

Install (npm):

npm install eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

Install (pnpm) — note that with auto-install-peers=false (the strict default in some setups) peer dependencies are not installed automatically, so they must be listed explicitly:

pnpm add eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

React 16.14 and 17 hosts remain supported and may keep matching react@^16.14.0 or react@^17.0.0 dependencies while migrating on their own schedule. The library is developed and tested against React 18.3.

If the host project relies on transitive copies of react/react-dom/moment from another package instead of declaring them directly, pnpm in isolated mode will not resolve our peer through them — the application must declare these three packages itself.

Other libraries previously listed as peer dependencies (final-form, final-form-arrays, react-final-form, react-final-form-arrays, prop-types) are now bundled as regular dependencies of eis-ui-render, so the host project does not need to install them.

eis-ui-render is consumed by a bundler — imported as a React component into a host application. Dropping dist/index.js into a page with a <script> tag is not supported: the UMD global lookup never matched React's real global name, so it has never worked.

Styles and assets (consumer)

The library entry deliberately does not inject CSS, so the host loads the stylesheet itself. Both paths below work and resolve to the same rules — dist/static/*.css are one-line @import re-exports, so the bytes ship only once:

import'eis-ui-render/static/all.css'// or 'eis-ui-render/dist/static/all.css'import'eis-ui-render/static/font.css'// icon font — only if the host does not provide its own

Some renderers reference images by absolute URL (<homepage>/static/images/… — flag icons for the language renderer, for example), so those files must also be reachable from the host's web root. Copy the package's static/ folder there as part of the build; it is self-contained:

cp -R node_modules/eis-ui-render/static ./public/

The meta.json contract (consumer)

The package ships the UI declaration contract as a JSON Schema (draft 2020-12) at eis-ui-render/meta.schema.json, so meta.json authors get autocomplete and validation in their editor instead of discovering a typo at render time.

The quickest way in is a pointer inside the file itself — no workspace configuration, and it works in VS Code and the JetBrains IDEs alike:

{
"$schema": "./node_modules/eis-ui-render/meta.schema.json",
"view": "Col",
"items": [{ "view": "Text", "name": "customer.name" }]
}

Or map it once for every meta file, in .vscode/settings.json:

{
"json.schemas": [
{
"fileMatch": ["**/*_meta.json", "**/meta.json"],
"url": "./node_modules/eis-ui-render/meta.schema.json"
}
]
}

The schema is permissive on purpose. Component attributes are forwarded to the underlying React component, so nodes accept properties the schema does not list, and view, render-method, action and normalizer names suggest the built-in vocabulary without rejecting an unlisted string — the renderer accepts those too. What the schema does constrain is the handful of shapes the engine genuinely requires (items/headers/extraItems/extraHeaders must be arrays, name must be a string), each of which is otherwise a render-time crash.

$schema is stripped before rendering, so adding it changes no output.

Dev-mode validation

The same rules run at runtime behind an opt-in prop. It is off by default and walks nothing until asked, so it costs a default host nothing:

<UIRenderdata={data}meta={meta}validateMeta={process.env.NODE_ENV!=='production'}/>

Each problem is reported to console.warn on one line, naming the JSON path of the offending node rather than leaving a stack trace inside a minified bundle:

[ui-render] meta error at "items[3].items[0].name": name must be a string key path, got number …
[ui-render] meta warning at "headers[2].renderCell": unknown render method "double5" …

error means the engine will fail on that node; warning means it will render, but silently degraded — an unknown view becomes a "field does not exist" placeholder, an unknown render* method falls back to plain text. Pass a function instead of true to collect the problems yourself (validateMeta={problems => …}); the reporter never throws into the host application, whatever it finds.

Contract version

meta.json may declare an optional root-level metaVersion ("MAJOR" or "MAJOR.MINOR") to record which contract it was authored against. The current contract version is 1.

  • Absence means "current" — the file targets whatever contract the installed eis-ui-render implements. That is the right choice when meta and library ship together, and it is why every existing meta.json keeps working untouched.
  • MAJOR changes only for a change that would break existing meta; MINOR for additive ones. Declaring a version equal to or below what the library implements is always compatible.
  • The engine ignores the value and strips the field before rendering: declaring it never changes output. Dev-mode validation is the only thing that reads it, and only to report a malformed value, a version newer than the installed library implements, or a metaVersion placed on a nested node, where it means nothing.

There is no negotiation beyond that, deliberately: the field exists so a future contract change can be additive and announced, not so hosts can request a different renderer.

Note the unrelated legacy version attribute seen in older meta files: it is not a contract version (existing files use it both as a producer version at the root and as a node label deeper in the tree), the engine discards it, and new files should use metaVersion.

Renderer configuration (consumer)

Three props configure how values are formatted and how the shell is labelled. They are published to every component the renderer draws, so a nested Table cell honours them exactly like a top-level field:

<UIRenderdata={data}meta={meta}dateFormat="DD/MM/YYYY"currency="EUR"language="fr"/>
PropDefaultEffect
dateFormatMM-DD-YYYYmoment format tokens for every date the renderer displays (an ISO value in a Text node, a render*: "Date" value) and edits (the date picker's display and parsing)
currencyUSDpublished as a CSS class on the renderer's shell (.app.EUR), for currency-specific styling
languageenpublished as a CSS class on the renderer's shell (.app.lang--fr)

Each is merged, not replaced: passing only dateFormat leaves currency and language at their inherited values. currency is notmeta.currencyCode — that one selects the currency symbol the value renderers print, and is declared in meta rather than passed as a prop.

These props used to be accepted and then silently ignored — every date rendered as MM-DD-YYYY whatever was passed. If your application has been passing dateFormat and compensating for it elsewhere, it now takes effect.

Error reporting (consumer)

The renderer catches a failure per node rather than letting one bad declaration blank the page: the failing node is replaced by a one-line diagnostic and everything around it keeps rendering. Pass onError to receive the same diagnostic as a structured report:

<UIRenderdata={data}meta={meta}onError={report=>Sentry.captureException(report.error,{extra: {metaPath: report.path,componentStack: report.errorInfo.componentStack},})}/>
{error,// the thrown valueerrorInfo,// React's {componentStack}path,// JSON path of the node in `meta`, e.g. 'items[3].items[0]' ('' = the root)props,// that node's resolved props: its meta declaration plus what the engine addedmessage,// the one-line diagnostic, also rendered in place of the failed node}

path is the point of the report — a stack trace out of a minified bundle names React internals, while items[3].items[0] names the declaration to go and fix. It is exact for a failure inside the component a node resolved to; for a failure the renderer hits while preparing a node (a malformed items, say) it names the closest enclosing node, which is the most precise position available.

The library logs the report itself as well, so onError adds a channel rather than silencing the console. It never has to be defensive: a reporter that throws is caught, and the render failure is still reported.

Development Installation

The published package declares engines.node >= 18: that is the floor for consuming it, and the shipped bundle needs nothing newer (its most modern syntax is optional chaining, and the packed artifact is verified to server-render on Node 22 and 24). Building this repository is a different matter and uses the version in .nvmrc, which is what CI installs.

  1. Install Node.js, if you haven't already — use the version in .nvmrc (v24).
  2. Navigate to project root folder and install dependencies by running this command in terminal:

npm install

Available Scripts

In the project directory, you can run:

npm run start

Runs the app in the development mode.
Open http://localhost:3001 to view it in Chrome browser, then activate LiveReload extension.

The page will reload if you make edits.
You will also see any lint errors in the console.

Live build mode

  • Install yalc globally npm install -g yalc
  • In your application add a link to the library with yalc add eis-ui-render --link and reinstall dependencies
  • Run npm run yalc-watch to build library and life reload

How to publish the library

  • Bump the package version with npm version patch (or minor, major, or an explicit version). This also synchronizes every tracked data-version attribute.
  • Inspect the package contents with npm pack --dry-run. The prepack lifecycle verifies version synchronization and builds the library automatically.
  • Verify the artifact with npm run test:pack. It enforces the packaging budgets and then packs, extracts and server-renders the tarball in a throwaway consumer that has only the three peer dependencies available. CI runs both gates on every pull request.
  • Login to npm with npm login if needed.
  • Publish the verified version with npm publish. The same prepack checks and build run again immediately before npm creates the published package.

Do not edit the version in package.json manually: use npm version so source metadata, the release commit, and the Git tag stay in sync.

How to publish on GitHub Pages

  • Run npm run build to prepare artifacts
  • Run npm run deploy to upload artifacts to GitHub

About

Recursive UI Rendering with Dynamic React Components

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,165 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Check the docs folder to get a basic understanding of the project's architecture

Demo

https://eisgroup.github.io/ui-render/

Supported view names

docs/SUPPORTED-VIEWS.md lists every name a meta.json may use — the view of a node, the value of a render* attribute, and the action names accepted by onClick, onChange and onDone — including the views that are declared as constants but that no resolver case handles. The page is generated from the FIELD constants plus the resolver source, so run npm run docs:views after adding or removing either; a contract test fails while the page and the source disagree.

Supported props on Table, Tooltip and Select / Dropdown

docs/SUPPORTED-PROPS.md documents the prop surface of the three views the semantic-ui-react exit replaces, split by what actually happens to each prop: consumed by our component, stripped before the DOM, forwarded to semantic-ui-react, or — once a view has been reimplemented — deliberately dropped. Table is in-house already and imports nothing; Tooltip and Select/Dropdown still wrap the package, so the page doubles as the parity checklist for the remaining work. It is generated from the component source and the call sites — run npm run docs:props after changing one — and a contract test additionally checks it against the example corpus.

Installation (consumer)

eis-ui-render declares the following peer dependencies. The host application must install them explicitly — they are not bundled. React must remain a single shared instance, while Moment must be supplied by the host because the library build externalizes it.

PackageRequired versionWhy it must be a peer
react^16.14.0 || ^17.0.0 || ^18.0.0A second copy of React in the tree triggers Invalid hook call and breaks Context (forms, providers). pnpm with strict node_modules will not deduplicate copies across non-overlapping ranges.
react-dom^16.14.0 || ^17.0.0 || ^18.0.0Must use the same major version as react so the renderer pair matches.
moment^2.29.4The library externalizes Moment and uses it for date pickers and formatters, so the host must provide a compatible 2.x version.

Install (npm):

npm install eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

Install (pnpm) — note that with auto-install-peers=false (the strict default in some setups) peer dependencies are not installed automatically, so they must be listed explicitly:

pnpm add eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

React 16.14 and 17 hosts remain supported and may keep matching react@^16.14.0 or react@^17.0.0 dependencies while migrating on their own schedule. The library is developed and tested against React 18.3.

If the host project relies on transitive copies of react/react-dom/moment from another package instead of declaring them directly, pnpm in isolated mode will not resolve our peer through them — the application must declare these three packages itself.

Other libraries previously listed as peer dependencies (final-form, final-form-arrays, react-final-form, react-final-form-arrays, prop-types) are now bundled as regular dependencies of eis-ui-render, so the host project does not need to install them.

eis-ui-render is consumed by a bundler — imported as a React component into a host application. Dropping dist/index.js into a page with a <script> tag is not supported: the UMD global lookup never matched React's real global name, so it has never worked.

Styles and assets (consumer)

The library entry deliberately does not inject CSS, so the host loads the stylesheet itself. Both paths below work and resolve to the same rules — dist/static/*.css are one-line @import re-exports, so the bytes ship only once:

import'eis-ui-render/static/all.css'// or 'eis-ui-render/dist/static/all.css'import'eis-ui-render/static/font.css'// icon font — only if the host does not provide its own

Some renderers reference images by absolute URL (<homepage>/static/images/… — flag icons for the language renderer, for example), so those files must also be reachable from the host's web root. Copy the package's static/ folder there as part of the build; it is self-contained:

cp -R node_modules/eis-ui-render/static ./public/

The meta.json contract (consumer)

The package ships the UI declaration contract as a JSON Schema (draft 2020-12) at eis-ui-render/meta.schema.json, so meta.json authors get autocomplete and validation in their editor instead of discovering a typo at render time.

The quickest way in is a pointer inside the file itself — no workspace configuration, and it works in VS Code and the JetBrains IDEs alike:

{
"$schema": "./node_modules/eis-ui-render/meta.schema.json",
"view": "Col",
"items": [{ "view": "Text", "name": "customer.name" }]
}

Or map it once for every meta file, in .vscode/settings.json:

{
"json.schemas": [
{
"fileMatch": ["**/*_meta.json", "**/meta.json"],
"url": "./node_modules/eis-ui-render/meta.schema.json"
}
]
}

The schema is permissive on purpose. Component attributes are forwarded to the underlying React component, so nodes accept properties the schema does not list, and view, render-method, action and normalizer names suggest the built-in vocabulary without rejecting an unlisted string — the renderer accepts those too. What the schema does constrain is the handful of shapes the engine genuinely requires (items/headers/extraItems/extraHeaders must be arrays, name must be a string), each of which is otherwise a render-time crash.

$schema is stripped before rendering, so adding it changes no output.

Dev-mode validation

The same rules run at runtime behind an opt-in prop. It is off by default and walks nothing until asked, so it costs a default host nothing:

<UIRenderdata={data}meta={meta}validateMeta={process.env.NODE_ENV!=='production'}/>

Each problem is reported to console.warn on one line, naming the JSON path of the offending node rather than leaving a stack trace inside a minified bundle:

[ui-render] meta error at "items[3].items[0].name": name must be a string key path, got number …
[ui-render] meta warning at "headers[2].renderCell": unknown render method "double5" …

error means the engine will fail on that node; warning means it will render, but silently degraded — an unknown view becomes a "field does not exist" placeholder, an unknown render* method falls back to plain text. Pass a function instead of true to collect the problems yourself (validateMeta={problems => …}); the reporter never throws into the host application, whatever it finds.

Contract version

meta.json may declare an optional root-level metaVersion ("MAJOR" or "MAJOR.MINOR") to record which contract it was authored against. The current contract version is 1.

  • Absence means "current" — the file targets whatever contract the installed eis-ui-render implements. That is the right choice when meta and library ship together, and it is why every existing meta.json keeps working untouched.
  • MAJOR changes only for a change that would break existing meta; MINOR for additive ones. Declaring a version equal to or below what the library implements is always compatible.
  • The engine ignores the value and strips the field before rendering: declaring it never changes output. Dev-mode validation is the only thing that reads it, and only to report a malformed value, a version newer than the installed library implements, or a metaVersion placed on a nested node, where it means nothing.

There is no negotiation beyond that, deliberately: the field exists so a future contract change can be additive and announced, not so hosts can request a different renderer.

Note the unrelated legacy version attribute seen in older meta files: it is not a contract version (existing files use it both as a producer version at the root and as a node label deeper in the tree), the engine discards it, and new files should use metaVersion.

Renderer configuration (consumer)

Three props configure how values are formatted and how the shell is labelled. They are published to every component the renderer draws, so a nested Table cell honours them exactly like a top-level field:

<UIRenderdata={data}meta={meta}dateFormat="DD/MM/YYYY"currency="EUR"language="fr"/>
PropDefaultEffect
dateFormatMM-DD-YYYYmoment format tokens for every date the renderer displays (an ISO value in a Text node, a render*: "Date" value) and edits (the date picker's display and parsing)
currencyUSDpublished as a CSS class on the renderer's shell (.app.EUR), for currency-specific styling
languageenpublished as a CSS class on the renderer's shell (.app.lang--fr)

Each is merged, not replaced: passing only dateFormat leaves currency and language at their inherited values. currency is notmeta.currencyCode — that one selects the currency symbol the value renderers print, and is declared in meta rather than passed as a prop.

These props used to be accepted and then silently ignored — every date rendered as MM-DD-YYYY whatever was passed. If your application has been passing dateFormat and compensating for it elsewhere, it now takes effect.

Error reporting (consumer)

The renderer catches a failure per node rather than letting one bad declaration blank the page: the failing node is replaced by a one-line diagnostic and everything around it keeps rendering. Pass onError to receive the same diagnostic as a structured report:

<UIRenderdata={data}meta={meta}onError={report=>Sentry.captureException(report.error,{extra: {metaPath: report.path,componentStack: report.errorInfo.componentStack},})}/>
{error,// the thrown valueerrorInfo,// React's {componentStack}path,// JSON path of the node in `meta`, e.g. 'items[3].items[0]' ('' = the root)props,// that node's resolved props: its meta declaration plus what the engine addedmessage,// the one-line diagnostic, also rendered in place of the failed node}

path is the point of the report — a stack trace out of a minified bundle names React internals, while items[3].items[0] names the declaration to go and fix. It is exact for a failure inside the component a node resolved to; for a failure the renderer hits while preparing a node (a malformed items, say) it names the closest enclosing node, which is the most precise position available.

The library logs the report itself as well, so onError adds a channel rather than silencing the console. It never has to be defensive: a reporter that throws is caught, and the render failure is still reported.

Development Installation

The published package declares engines.node >= 18: that is the floor for consuming it, and the shipped bundle needs nothing newer (its most modern syntax is optional chaining, and the packed artifact is verified to server-render on Node 22 and 24). Building this repository is a different matter and uses the version in .nvmrc, which is what CI installs.

  1. Install Node.js, if you haven't already — use the version in .nvmrc (v24).
  2. Navigate to project root folder and install dependencies by running this command in terminal:

npm install

Available Scripts

In the project directory, you can run:

npm run start

Runs the app in the development mode.
Open http://localhost:3001 to view it in Chrome browser, then activate LiveReload extension.

The page will reload if you make edits.
You will also see any lint errors in the console.

Live build mode

  • Install yalc globally npm install -g yalc
  • In your application add a link to the library with yalc add eis-ui-render --link and reinstall dependencies
  • Run npm run yalc-watch to build library and life reload

How to publish the library

  • Bump the package version with npm version patch (or minor, major, or an explicit version). This also synchronizes every tracked data-version attribute.
  • Inspect the package contents with npm pack --dry-run. The prepack lifecycle verifies version synchronization and builds the library automatically.
  • Verify the artifact with npm run test:pack. It enforces the packaging budgets and then packs, extracts and server-renders the tarball in a throwaway consumer that has only the three peer dependencies available. CI runs both gates on every pull request.
  • Login to npm with npm login if needed.
  • Publish the verified version with npm publish. The same prepack checks and build run again immediately before npm creates the published package.

Do not edit the version in package.json manually: use npm version so source metadata, the release commit, and the Git tag stay in sync.

How to publish on GitHub Pages

  • Run npm run build to prepare artifacts
  • Run npm run deploy to upload artifacts to GitHub

About

Recursive UI Rendering with Dynamic React Components

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

1,165 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Check the docs folder to get a basic understanding of the project's architecture

Demo

https://eisgroup.github.io/ui-render/

Supported view names

docs/SUPPORTED-VIEWS.md lists every name a meta.json may use — the view of a node, the value of a render* attribute, and the action names accepted by onClick, onChange and onDone — including the views that are declared as constants but that no resolver case handles. The page is generated from the FIELD constants plus the resolver source, so run npm run docs:views after adding or removing either; a contract test fails while the page and the source disagree.

Supported props on Table, Tooltip and Select / Dropdown

docs/SUPPORTED-PROPS.md documents the prop surface of the three views the semantic-ui-react exit replaces, split by what actually happens to each prop: consumed by our component, stripped before the DOM, forwarded to semantic-ui-react, or — once a view has been reimplemented — deliberately dropped. Table is in-house already and imports nothing; Tooltip and Select/Dropdown still wrap the package, so the page doubles as the parity checklist for the remaining work. It is generated from the component source and the call sites — run npm run docs:props after changing one — and a contract test additionally checks it against the example corpus.

Installation (consumer)

eis-ui-render declares the following peer dependencies. The host application must install them explicitly — they are not bundled. React must remain a single shared instance, while Moment must be supplied by the host because the library build externalizes it.

PackageRequired versionWhy it must be a peer
react^16.14.0 || ^17.0.0 || ^18.0.0A second copy of React in the tree triggers Invalid hook call and breaks Context (forms, providers). pnpm with strict node_modules will not deduplicate copies across non-overlapping ranges.
react-dom^16.14.0 || ^17.0.0 || ^18.0.0Must use the same major version as react so the renderer pair matches.
moment^2.29.4The library externalizes Moment and uses it for date pickers and formatters, so the host must provide a compatible 2.x version.

Install (npm):

npm install eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

Install (pnpm) — note that with auto-install-peers=false (the strict default in some setups) peer dependencies are not installed automatically, so they must be listed explicitly:

pnpm add eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

React 16.14 and 17 hosts remain supported and may keep matching react@^16.14.0 or react@^17.0.0 dependencies while migrating on their own schedule. The library is developed and tested against React 18.3.

If the host project relies on transitive copies of react/react-dom/moment from another package instead of declaring them directly, pnpm in isolated mode will not resolve our peer through them — the application must declare these three packages itself.

Other libraries previously listed as peer dependencies (final-form, final-form-arrays, react-final-form, react-final-form-arrays, prop-types) are now bundled as regular dependencies of eis-ui-render, so the host project does not need to install them.

eis-ui-render is consumed by a bundler — imported as a React component into a host application. Dropping dist/index.js into a page with a <script> tag is not supported: the UMD global lookup never matched React's real global name, so it has never worked.

Styles and assets (consumer)

The library entry deliberately does not inject CSS, so the host loads the stylesheet itself. Both paths below work and resolve to the same rules — dist/static/*.css are one-line @import re-exports, so the bytes ship only once:

import'eis-ui-render/static/all.css'// or 'eis-ui-render/dist/static/all.css'import'eis-ui-render/static/font.css'// icon font — only if the host does not provide its own

Some renderers reference images by absolute URL (<homepage>/static/images/… — flag icons for the language renderer, for example), so those files must also be reachable from the host's web root. Copy the package's static/ folder there as part of the build; it is self-contained:

cp -R node_modules/eis-ui-render/static ./public/

The meta.json contract (consumer)

The package ships the UI declaration contract as a JSON Schema (draft 2020-12) at eis-ui-render/meta.schema.json, so meta.json authors get autocomplete and validation in their editor instead of discovering a typo at render time.

The quickest way in is a pointer inside the file itself — no workspace configuration, and it works in VS Code and the JetBrains IDEs alike:

{
"$schema": "./node_modules/eis-ui-render/meta.schema.json",
"view": "Col",
"items": [{ "view": "Text", "name": "customer.name" }]
}

Or map it once for every meta file, in .vscode/settings.json:

{
"json.schemas": [
{
"fileMatch": ["**/*_meta.json", "**/meta.json"],
"url": "./node_modules/eis-ui-render/meta.schema.json"
}
]
}

The schema is permissive on purpose. Component attributes are forwarded to the underlying React component, so nodes accept properties the schema does not list, and view, render-method, action and normalizer names suggest the built-in vocabulary without rejecting an unlisted string — the renderer accepts those too. What the schema does constrain is the handful of shapes the engine genuinely requires (items/headers/extraItems/extraHeaders must be arrays, name must be a string), each of which is otherwise a render-time crash.

$schema is stripped before rendering, so adding it changes no output.

Dev-mode validation

The same rules run at runtime behind an opt-in prop. It is off by default and walks nothing until asked, so it costs a default host nothing:

<UIRenderdata={data}meta={meta}validateMeta={process.env.NODE_ENV!=='production'}/>

Each problem is reported to console.warn on one line, naming the JSON path of the offending node rather than leaving a stack trace inside a minified bundle:

[ui-render] meta error at "items[3].items[0].name": name must be a string key path, got number …
[ui-render] meta warning at "headers[2].renderCell": unknown render method "double5" …

error means the engine will fail on that node; warning means it will render, but silently degraded — an unknown view becomes a "field does not exist" placeholder, an unknown render* method falls back to plain text. Pass a function instead of true to collect the problems yourself (validateMeta={problems => …}); the reporter never throws into the host application, whatever it finds.

Contract version

meta.json may declare an optional root-level metaVersion ("MAJOR" or "MAJOR.MINOR") to record which contract it was authored against. The current contract version is 1.

  • Absence means "current" — the file targets whatever contract the installed eis-ui-render implements. That is the right choice when meta and library ship together, and it is why every existing meta.json keeps working untouched.
  • MAJOR changes only for a change that would break existing meta; MINOR for additive ones. Declaring a version equal to or below what the library implements is always compatible.
  • The engine ignores the value and strips the field before rendering: declaring it never changes output. Dev-mode validation is the only thing that reads it, and only to report a malformed value, a version newer than the installed library implements, or a metaVersion placed on a nested node, where it means nothing.

There is no negotiation beyond that, deliberately: the field exists so a future contract change can be additive and announced, not so hosts can request a different renderer.

Note the unrelated legacy version attribute seen in older meta files: it is not a contract version (existing files use it both as a producer version at the root and as a node label deeper in the tree), the engine discards it, and new files should use metaVersion.

Renderer configuration (consumer)

Three props configure how values are formatted and how the shell is labelled. They are published to every component the renderer draws, so a nested Table cell honours them exactly like a top-level field:

<UIRenderdata={data}meta={meta}dateFormat="DD/MM/YYYY"currency="EUR"language="fr"/>
PropDefaultEffect
dateFormatMM-DD-YYYYmoment format tokens for every date the renderer displays (an ISO value in a Text node, a render*: "Date" value) and edits (the date picker's display and parsing)
currencyUSDpublished as a CSS class on the renderer's shell (.app.EUR), for currency-specific styling
languageenpublished as a CSS class on the renderer's shell (.app.lang--fr)

Each is merged, not replaced: passing only dateFormat leaves currency and language at their inherited values. currency is notmeta.currencyCode — that one selects the currency symbol the value renderers print, and is declared in meta rather than passed as a prop.

These props used to be accepted and then silently ignored — every date rendered as MM-DD-YYYY whatever was passed. If your application has been passing dateFormat and compensating for it elsewhere, it now takes effect.

Error reporting (consumer)

The renderer catches a failure per node rather than letting one bad declaration blank the page: the failing node is replaced by a one-line diagnostic and everything around it keeps rendering. Pass onError to receive the same diagnostic as a structured report:

<UIRenderdata={data}meta={meta}onError={report=>Sentry.captureException(report.error,{extra: {metaPath: report.path,componentStack: report.errorInfo.componentStack},})}/>
{error,// the thrown valueerrorInfo,// React's {componentStack}path,// JSON path of the node in `meta`, e.g. 'items[3].items[0]' ('' = the root)props,// that node's resolved props: its meta declaration plus what the engine addedmessage,// the one-line diagnostic, also rendered in place of the failed node}

path is the point of the report — a stack trace out of a minified bundle names React internals, while items[3].items[0] names the declaration to go and fix. It is exact for a failure inside the component a node resolved to; for a failure the renderer hits while preparing a node (a malformed items, say) it names the closest enclosing node, which is the most precise position available.

The library logs the report itself as well, so onError adds a channel rather than silencing the console. It never has to be defensive: a reporter that throws is caught, and the render failure is still reported.

Development Installation

The published package declares engines.node >= 18: that is the floor for consuming it, and the shipped bundle needs nothing newer (its most modern syntax is optional chaining, and the packed artifact is verified to server-render on Node 22 and 24). Building this repository is a different matter and uses the version in .nvmrc, which is what CI installs.

  1. Install Node.js, if you haven't already — use the version in .nvmrc (v24).
  2. Navigate to project root folder and install dependencies by running this command in terminal:

npm install

Available Scripts

In the project directory, you can run:

npm run start

Runs the app in the development mode.
Open http://localhost:3001 to view it in Chrome browser, then activate LiveReload extension.

The page will reload if you make edits.
You will also see any lint errors in the console.

Live build mode

  • Install yalc globally npm install -g yalc
  • In your application add a link to the library with yalc add eis-ui-render --link and reinstall dependencies
  • Run npm run yalc-watch to build library and life reload

How to publish the library

  • Bump the package version with npm version patch (or minor, major, or an explicit version). This also synchronizes every tracked data-version attribute.
  • Inspect the package contents with npm pack --dry-run. The prepack lifecycle verifies version synchronization and builds the library automatically.
  • Verify the artifact with npm run test:pack. It enforces the packaging budgets and then packs, extracts and server-renders the tarball in a throwaway consumer that has only the three peer dependencies available. CI runs both gates on every pull request.
  • Login to npm with npm login if needed.
  • Publish the verified version with npm publish. The same prepack checks and build run again immediately before npm creates the published package.

Do not edit the version in package.json manually: use npm version so source metadata, the release commit, and the Git tag stay in sync.

How to publish on GitHub Pages

  • Run npm run build to prepare artifacts
  • Run npm run deploy to upload artifacts to GitHub

About

Recursive UI Rendering with Dynamic React Components

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

1,165 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Check the docs folder to get a basic understanding of the project's architecture

Demo

https://eisgroup.github.io/ui-render/

Supported view names

docs/SUPPORTED-VIEWS.md lists every name a meta.json may use — the view of a node, the value of a render* attribute, and the action names accepted by onClick, onChange and onDone — including the views that are declared as constants but that no resolver case handles. The page is generated from the FIELD constants plus the resolver source, so run npm run docs:views after adding or removing either; a contract test fails while the page and the source disagree.

Supported props on Table, Tooltip and Select / Dropdown

docs/SUPPORTED-PROPS.md documents the prop surface of the three views the semantic-ui-react exit replaces, split by what actually happens to each prop: consumed by our component, stripped before the DOM, forwarded to semantic-ui-react, or — once a view has been reimplemented — deliberately dropped. Table is in-house already and imports nothing; Tooltip and Select/Dropdown still wrap the package, so the page doubles as the parity checklist for the remaining work. It is generated from the component source and the call sites — run npm run docs:props after changing one — and a contract test additionally checks it against the example corpus.

Installation (consumer)

eis-ui-render declares the following peer dependencies. The host application must install them explicitly — they are not bundled. React must remain a single shared instance, while Moment must be supplied by the host because the library build externalizes it.

PackageRequired versionWhy it must be a peer
react^16.14.0 || ^17.0.0 || ^18.0.0A second copy of React in the tree triggers Invalid hook call and breaks Context (forms, providers). pnpm with strict node_modules will not deduplicate copies across non-overlapping ranges.
react-dom^16.14.0 || ^17.0.0 || ^18.0.0Must use the same major version as react so the renderer pair matches.
moment^2.29.4The library externalizes Moment and uses it for date pickers and formatters, so the host must provide a compatible 2.x version.

Install (npm):

npm install eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

Install (pnpm) — note that with auto-install-peers=false (the strict default in some setups) peer dependencies are not installed automatically, so they must be listed explicitly:

pnpm add eis-ui-render react@^18.0.0 react-dom@^18.0.0 moment@^2.29.4

React 16.14 and 17 hosts remain supported and may keep matching react@^16.14.0 or react@^17.0.0 dependencies while migrating on their own schedule. The library is developed and tested against React 18.3.

If the host project relies on transitive copies of react/react-dom/moment from another package instead of declaring them directly, pnpm in isolated mode will not resolve our peer through them — the application must declare these three packages itself.

Other libraries previously listed as peer dependencies (final-form, final-form-arrays, react-final-form, react-final-form-arrays, prop-types) are now bundled as regular dependencies of eis-ui-render, so the host project does not need to install them.

eis-ui-render is consumed by a bundler — imported as a React component into a host application. Dropping dist/index.js into a page with a <script> tag is not supported: the UMD global lookup never matched React's real global name, so it has never worked.

Styles and assets (consumer)

The library entry deliberately does not inject CSS, so the host loads the stylesheet itself. Both paths below work and resolve to the same rules — dist/static/*.css are one-line @import re-exports, so the bytes ship only once:

import'eis-ui-render/static/all.css'// or 'eis-ui-render/dist/static/all.css'import'eis-ui-render/static/font.css'// icon font — only if the host does not provide its own

Some renderers reference images by absolute URL (<homepage>/static/images/… — flag icons for the language renderer, for example), so those files must also be reachable from the host's web root. Copy the package's static/ folder there as part of the build; it is self-contained:

cp -R node_modules/eis-ui-render/static ./public/

The meta.json contract (consumer)

The package ships the UI declaration contract as a JSON Schema (draft 2020-12) at eis-ui-render/meta.schema.json, so meta.json authors get autocomplete and validation in their editor instead of discovering a typo at render time.

The quickest way in is a pointer inside the file itself — no workspace configuration, and it works in VS Code and the JetBrains IDEs alike:

{
"$schema": "./node_modules/eis-ui-render/meta.schema.json",
"view": "Col",
"items": [{ "view": "Text", "name": "customer.name" }]
}

Or map it once for every meta file, in .vscode/settings.json:

{
"json.schemas": [
{
"fileMatch": ["**/*_meta.json", "**/meta.json"],
"url": "./node_modules/eis-ui-render/meta.schema.json"
}
]
}

The schema is permissive on purpose. Component attributes are forwarded to the underlying React component, so nodes accept properties the schema does not list, and view, render-method, action and normalizer names suggest the built-in vocabulary without rejecting an unlisted string — the renderer accepts those too. What the schema does constrain is the handful of shapes the engine genuinely requires (items/headers/extraItems/extraHeaders must be arrays, name must be a string), each of which is otherwise a render-time crash.

$schema is stripped before rendering, so adding it changes no output.

Dev-mode validation

The same rules run at runtime behind an opt-in prop. It is off by default and walks nothing until asked, so it costs a default host nothing:

<UIRenderdata={data}meta={meta}validateMeta={process.env.NODE_ENV!=='production'}/>

Each problem is reported to console.warn on one line, naming the JSON path of the offending node rather than leaving a stack trace inside a minified bundle:

[ui-render] meta error at "items[3].items[0].name": name must be a string key path, got number …
[ui-render] meta warning at "headers[2].renderCell": unknown render method "double5" …

error means the engine will fail on that node; warning means it will render, but silently degraded — an unknown view becomes a "field does not exist" placeholder, an unknown render* method falls back to plain text. Pass a function instead of true to collect the problems yourself (validateMeta={problems => …}); the reporter never throws into the host application, whatever it finds.

Contract version

meta.json may declare an optional root-level metaVersion ("MAJOR" or "MAJOR.MINOR") to record which contract it was authored against. The current contract version is 1.

  • Absence means "current" — the file targets whatever contract the installed eis-ui-render implements. That is the right choice when meta and library ship together, and it is why every existing meta.json keeps working untouched.
  • MAJOR changes only for a change that would break existing meta; MINOR for additive ones. Declaring a version equal to or below what the library implements is always compatible.
  • The engine ignores the value and strips the field before rendering: declaring it never changes output. Dev-mode validation is the only thing that reads it, and only to report a malformed value, a version newer than the installed library implements, or a metaVersion placed on a nested node, where it means nothing.

There is no negotiation beyond that, deliberately: the field exists so a future contract change can be additive and announced, not so hosts can request a different renderer.

Note the unrelated legacy version attribute seen in older meta files: it is not a contract version (existing files use it both as a producer version at the root and as a node label deeper in the tree), the engine discards it, and new files should use metaVersion.

Renderer configuration (consumer)

Three props configure how values are formatted and how the shell is labelled. They are published to every component the renderer draws, so a nested Table cell honours them exactly like a top-level field:

<UIRenderdata={data}meta={meta}dateFormat="DD/MM/YYYY"currency="EUR"language="fr"/>
PropDefaultEffect
dateFormatMM-DD-YYYYmoment format tokens for every date the renderer displays (an ISO value in a Text node, a render*: "Date" value) and edits (the date picker's display and parsing)
currencyUSDpublished as a CSS class on the renderer's shell (.app.EUR), for currency-specific styling
languageenpublished as a CSS class on the renderer's shell (.app.lang--fr)

Each is merged, not replaced: passing only dateFormat leaves currency and language at their inherited values. currency is notmeta.currencyCode — that one selects the currency symbol the value renderers print, and is declared in meta rather than passed as a prop.

These props used to be accepted and then silently ignored — every date rendered as MM-DD-YYYY whatever was passed. If your application has been passing dateFormat and compensating for it elsewhere, it now takes effect.

Error reporting (consumer)

The renderer catches a failure per node rather than letting one bad declaration blank the page: the failing node is replaced by a one-line diagnostic and everything around it keeps rendering. Pass onError to receive the same diagnostic as a structured report:

<UIRenderdata={data}meta={meta}onError={report=>Sentry.captureException(report.error,{extra: {metaPath: report.path,componentStack: report.errorInfo.componentStack},})}/>
{error,// the thrown valueerrorInfo,// React's {componentStack}path,// JSON path of the node in `meta`, e.g. 'items[3].items[0]' ('' = the root)props,// that node's resolved props: its meta declaration plus what the engine addedmessage,// the one-line diagnostic, also rendered in place of the failed node}

path is the point of the report — a stack trace out of a minified bundle names React internals, while items[3].items[0] names the declaration to go and fix. It is exact for a failure inside the component a node resolved to; for a failure the renderer hits while preparing a node (a malformed items, say) it names the closest enclosing node, which is the most precise position available.

The library logs the report itself as well, so onError adds a channel rather than silencing the console. It never has to be defensive: a reporter that throws is caught, and the render failure is still reported.

Development Installation

The published package declares engines.node >= 18: that is the floor for consuming it, and the shipped bundle needs nothing newer (its most modern syntax is optional chaining, and the packed artifact is verified to server-render on Node 22 and 24). Building this repository is a different matter and uses the version in .nvmrc, which is what CI installs.

  1. Install Node.js, if you haven't already — use the version in .nvmrc (v24).
  2. Navigate to project root folder and install dependencies by running this command in terminal:

npm install

Available Scripts

In the project directory, you can run:

npm run start

Runs the app in the development mode.
Open http://localhost:3001 to view it in Chrome browser, then activate LiveReload extension.

The page will reload if you make edits.
You will also see any lint errors in the console.

Live build mode

  • Install yalc globally npm install -g yalc
  • In your application add a link to the library with yalc add eis-ui-render --link and reinstall dependencies
  • Run npm run yalc-watch to build library and life reload

How to publish the library

  • Bump the package version with npm version patch (or minor, major, or an explicit version). This also synchronizes every tracked data-version attribute.
  • Inspect the package contents with npm pack --dry-run. The prepack lifecycle verifies version synchronization and builds the library automatically.
  • Verify the artifact with npm run test:pack. It enforces the packaging budgets and then packs, extracts and server-renders the tarball in a throwaway consumer that has only the three peer dependencies available. CI runs both gates on every pull request.
  • Login to npm with npm login if needed.
  • Publish the verified version with npm publish. The same prepack checks and build run again immediately before npm creates the published package.

Do not edit the version in package.json manually: use npm version so source metadata, the release commit, and the Git tag stay in sync.

How to publish on GitHub Pages

  • Run npm run build to prepare artifacts
  • Run npm run deploy to upload artifacts to GitHub

About

Recursive UI Rendering with Dynamic React Components

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages