And indeed I was correct! Not quite a complete copy yet of the tables example from the pdfmaker site but close enough to show off the possibilities. I'll finish it off when I can and submit to the Flows site.
But if you'd care to try it, this might be a useful alternative to pdfmaker since most OS's these days will let you print to PDF.
[{"id":"279f069488faedf5","type":"uibuilder","z":"a70b22bf53a7ea9c","name":"","topic":"","url":"ui-tests","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"iife-blank-client","extTemplate":"","showfolder":false,"reload":true,"sourceFolder":"src","deployedVersion":"6.1.0","showMsgUib":true,"x":440,"y":160,"wires":[["2c2f7fac82a12c69"],["df4025edc7711edd"]],"info":"This example uses a blank template with\r\nthe IIFE build of the front-end client.\r\n\r\nIt does not use any front-end framework, just\r\npure HTML, CSS and JavaScript.\r\n\r\nThe IIFE build should be included using a link\r\ntag in your HTML."},{"id":"1b2196df2b92f409","type":"inject","z":"a70b22bf53a7ea9c","name":"Send a msg","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"A Message From Node-RED","payload":"","payloadType":"date","x":180,"y":180,"wires":[["d621dd0923f1e188"]],"info":"Send a simply msg to the front-end.\r\n\r\nThe default front-end template code will display the msg\r\nusing HTML formatting, no coding required."},{"id":"767ae0f52380b47d","type":"inject","z":"a70b22bf53a7ea9c","name":"Reload","props":[{"p":"_ui","v":"{\"method\":\"reload\"}","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"reload","x":160,"y":220,"wires":[["d621dd0923f1e188"]],"info":"Sends a pre-formatted msg to the front-end that\r\ncauses the page to reload itself."},{"id":"e835cac509084abe","type":"inject","z":"a70b22bf53a7ea9c","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"\"This is the payload from the inject node! Random number: \" & $formatInteger($random()*100, \"0\")","payloadType":"jsonata","x":125,"y":100,"wires":[["ada5575de6d2dc38"]],"l":false},{"id":"ada5575de6d2dc38","type":"function","z":"a70b22bf53a7ea9c","name":"Notification","func":"msg = {\n \"_uib\": {\n // This can actually be anything, if it doesn't exist, \n // the toast will appear in the default location\n \"componentRef\": \"globalNotification\",\n // Note that most if not all of these are optional\n \"options\": {\n // These can contain HTML - note the inclusion of the payload from the upstram msg\n \"title\": \"This is the <i>title</i>\",\n \"content\": `This is content <span style=\\\"color:red;\\\">in addition to</span> the payload<p>${msg.payload}</p>`,\n \n // Use 1 of the following 2 - click msg if no auto hide:\n \"autoHideDelay\": 2500,\n // \"noAutoHide\": true,\n\n // If false or not included, msgs stack above each other.\n \"appendToast\": true,\n\n // See \"Recommended surfaces\" in uib-brand.css. Normally\n // 'primary', 'secondary', 'success', 'info', 'warn', 'warning', 'failure', 'error', 'danger'\n \"variant\": \"info\",\n }\n }\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":240,"y":100,"wires":[["d621dd0923f1e188"]],"info":"Overlays a message on top of your UI.\r\n\r\nThe message removes itself after a couple of seconds.\r\n\r\nYou can change the options property to change the look\r\nof the displayed message."},{"id":"96f771bbdcc816fa","type":"inject","z":"a70b22bf53a7ea9c","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":125,"y":140,"wires":[["961d42bbb9613525"]],"l":false},{"id":"961d42bbb9613525","type":"function","z":"a70b22bf53a7ea9c","name":"New Card","func":"let cardCounter = context.get('cardCounter') ?? 0\n\nmsg = {\n \"_ui\": [\n {\n \"method\": \"remove\",\n \"components\": [\n \"#mycard\"\n ]\n },\n {\n \"method\": \"add\",\n \"parent\": \"#more\",\n \"components\": [\n {\n \"type\": \"div\",\n \"attributes\": {\n \"id\": \"mycard\",\n \"title\": \"This is my Card\",\n \"style\": \"max-width: 20rem;border:solid silver 1px;margin-bottom:1rem;\",\n },\n \"components\": [\n {\n \"type\": \"h2\",\n \"slot\": \"A New Card\",\n \"attributes\": {\n \"class\": \"complementary\",\n \"style\": \"text-align:center;margin-top:0;\"\n }\n },\n {\n \"type\": \"p\",\n \"slot\": \"Some text in a paragraph.\"\n },\n {\n \"type\": \"p\",\n \"slot\": \"Another paragraph. Count: \" + ++cardCounter\n }\n ]\n }\n ],\n }\n ]\n}\ncontext.set('cardCounter', cardCounter)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":230,"y":140,"wires":[["d621dd0923f1e188"]],"info":"Inserts a pure HTML \"card\" into a div called `#more`.\r\nIf that div does not exist, will add to the bottom of the HTML.\r\n\r\nFirstly attempts to remove the div so that you only ever have 1.\r\n\r\nAn example of using uibuilder's dynamic UI configuration-driven\r\nbuilding capabilities without the need for any fancy nodes or\r\nframeworks. Pure HTML. But you can still utilise the extra\r\nfeatures of your favourite framework too if you like!"},{"id":"2c2f7fac82a12c69","type":"debug","z":"a70b22bf53a7ea9c","name":"uibuilder standard output","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":655,"y":120,"wires":[],"l":false,"info":"This shows the data coming out of the\r\nuibuilder node's Port #1 (top) which is\r\nthe standard output.\r\n\r\nHere you will see any standard msg sent from\r\nyour front-end code."},{"id":"df4025edc7711edd","type":"debug","z":"a70b22bf53a7ea9c","name":"uibuilder control output","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":655,"y":180,"wires":[],"l":false,"info":"This shows the data coming out of the\r\nuibuilder node's Port #2 (bottom) which is\r\nthe control output.\r\n\r\nHere you will see any control msg either sent\r\nby the node itself or from the front-end library.\r\n\r\nFor example the \"client disconnect\" and\r\n\"client connect\" messages. Or the \"visibility\"\r\nmessages from the client.\r\n\r\nLoop the \"client connect\", \"cache replay\" and\r\n\"cache clear\" messages back to a `uib-cache`\r\nnode before the input to uibuilder in order\r\nto control the output of the cache."},{"id":"5b40492405adf766","type":"comment","z":"a70b22bf53a7ea9c","name":"Chk Description in each node","info":"","x":490,"y":100,"wires":[]},{"id":"e8f0471862ab3f9c","type":"uib-sender","z":"a70b22bf53a7ea9c","url":"ui-tests","name":"","topic":"","passthrough":false,"return":false,"outputs":0,"x":580,"y":280,"wires":[]},{"id":"73e6d30f1e961bab","type":"inject","z":"a70b22bf53a7ea9c","name":"","props":[{"p":"_ui","v":"[{\"method\":\"remove\",\"components\":[\"#ui-test\"]},{\"method\":\"add\",\"components\":[{\"type\":\"main\",\"id\":\"ui-test\",\"parent\":\"#more\",\"components\":[{\"type\":\"h2\",\"slot\":\"Tables\"},{\"type\":\"p\",\"slot\":\"This is an example of using uibuilder's low-code, config-driven page builder. It is based on the TABLES example from <a href='http://pdfmake.org/playground.html' target='_blank'>pdfmake</a>. This is partly to demonstrate that pdfmake and uibuilder use related principals for similar outcomes.\"},{\"type\":\"article\",\"components\":[{\"type\":\"h3\",\"slot\":\"A simple table (no style overrides)\"},{\"type\":\"p\",\"slot\":\"Nothing more than a couple of unstyled rows and columns. No headings.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Column 1\"},{\"type\":\"td\",\"slot\":\"Column 2\"},{\"type\":\"td\",\"slot\":\"Column 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"One value goes here\"},{\"type\":\"td\",\"slot\":\"Another one here\"},{\"type\":\"td\",\"slot\":\"OK\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h3\",\"slot\":\"A simple table with nested elements\"},{\"type\":\"p\",\"slot\":\"It is of course possible to nest any other type of nodes available in <del>pdfmake</del> uibuilder/HTML inside table cells.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Column 1\"},{\"type\":\"td\",\"slot\":\"Column 2\"},{\"type\":\"td\",\"slot\":\"Column 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Let's try an unordered list\",\"components\":[{\"type\":\"ul\",\"components\":[{\"type\":\"li\",\"slot\":\"Item 1\"},{\"type\":\"li\",\"slot\":\"Item 2\"}]}]},{\"type\":\"td\",\"slot\":\"or a nested table\",\"components\":[{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"th\",\"slot\":\"Col 1\"},{\"type\":\"th\",\"slot\":\"Col 2\"},{\"type\":\"th\",\"slot\":\"Col 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"R1C1\"},{\"type\":\"td\",\"slot\":\"R1C2\"},{\"type\":\"td\",\"slot\":\"R1C3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"R2C1\"},{\"type\":\"td\",\"slot\":\"R2C2\"},{\"type\":\"td\",\"slot\":\"R2C3\"}]}]}]},{\"type\":\"td\",\"slotMarkdown\":\"Inlines can be _styled_ easily as everywhere else. Even using Markdown!\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h3\",\"slot\":\"Defining column widths\"},{\"type\":\"p\",\"slotMarkdown\":\"~~Tables support the same width definitions as standard columns~~ HTML is different to pdfmaker here since styling is done using CSS.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"width:100em;\"},\"slot\":\"width:100em\"},{\"type\":\"td\",\"slot\":\"Unsized\"},{\"type\":\"td\",\"attributes\":{\"style\":\"width:25%;\"},\"slot\":\"width:25%\"},{\"type\":\"td\",\"slot\":\"Unsized\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"fixed-width cells have exactly the specified width\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"}]}]},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"width:90%;\"},\"slotMarkdown\":\"This is a ~~star-sized~~ fixed % size column. The next column over, an auto-sized column, will wrap to accomodate all the text in this cell.\"},{\"type\":\"td\",\"slot\":\"I am auto sized.\"}]}]},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slotMarkdown\":\"This is ~~a star-sized~~ an unsized column. The next column over, also auto-sized, will not wrap to accomodate all the text in this cell, because it has been given the noWrap style.\"},{\"type\":\"td\",\"attributes\":{\"style\":\"white-space: nowrap;\"},\"slot\":\"I am no-wrap auto sized.\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h3\",\"slot\":\"Defining row heights\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:2em;\"},\"slot\":\"row 1 with height 2em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:5em;\"},\"slot\":\"row 2 with height 5em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:7em;\"},\"slot\":\"row 3 with height 7em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]}]}]}]}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"tables","x":170,"y":280,"wires":[["e8f0471862ab3f9c"]]},{"id":"d621dd0923f1e188","type":"junction","z":"a70b22bf53a7ea9c","x":370,"y":160,"wires":[["279f069488faedf5"]]}]
Here showing in dark mode but you can force to light or simply change the dark/light setting on your browser. The example even includes both a safe HTML filter AND the ability to output content as Markdown not just HTML - both thanks to built-in support for 2 optional external libraries (dompurify and markdown-it).