@flowfuse/node-red-dashboard 1.33.0 → 1.33.1-215bca8-202610211539.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/nodes/config/locales/en-US/ui_base.html +9 -0
  2. package/nodes/plugins/vue-editor.html +108 -0
  3. package/nodes/widgets/locales/en-US/ui_template.json +3 -0
  4. package/nodes/widgets/ui_template.html +20 -6
  5. package/package.json +19 -11
  6. package/resources/vue-editor/vue-script.mjs +148 -0
  7. package/resources/vue-editor/vue-sfc.mjs +618 -0
  8. package/dist/apple-touch-icon.png +0 -0
  9. package/dist/assets/abnfDiagram-N423BO3Z-D6p5Ez-2.js +0 -1
  10. package/dist/assets/architectureDiagram-T3A2C74G-BxKJJ8QP.js +0 -36
  11. package/dist/assets/blockDiagram-VBNYF7ZC-DHq5Ey-Y.js +0 -132
  12. package/dist/assets/c4Diagram-5PPSVZJV-CMejj2Pw.js +0 -10
  13. package/dist/assets/chunk-2GRJ4B5K-DfKrdElA.js +0 -1
  14. package/dist/assets/chunk-2Q5K7J3B-Nm9OtfXP.js +0 -1
  15. package/dist/assets/chunk-5RXB4S5H-90AhyXTK.js +0 -231
  16. package/dist/assets/chunk-5VM5RSS4-B-TvGvkW.js +0 -15
  17. package/dist/assets/chunk-6Q2QTUOP-ikWdh2bV.js +0 -88
  18. package/dist/assets/chunk-GF5L2VYU-Bkx4752U.js +0 -206
  19. package/dist/assets/chunk-JWPE2WC7-CXOxwsI-.js +0 -1
  20. package/dist/assets/chunk-KBJHAD2P-CZxnMJoj.js +0 -1
  21. package/dist/assets/chunk-RYQCIY6F-DQKvWoM9.js +0 -1
  22. package/dist/assets/chunk-XXDRQBXY-CvLqB_x5.js +0 -1
  23. package/dist/assets/classDiagram-JCYQIIEL-DzhOke_T.js +0 -1
  24. package/dist/assets/classDiagram-v2-OCEON4UE-DzhOke_T.js +0 -1
  25. package/dist/assets/cose-bilkent-JH36ORCC-BRLAqXvh.js +0 -1
  26. package/dist/assets/cynefin-VYW2F7L2-BRmQaHNR.js +0 -178
  27. package/dist/assets/cynefinDiagram-MW4NZA55-YeJQ9awi.js +0 -62
  28. package/dist/assets/cytoscape.esm-D_LviqZs.js +0 -331
  29. package/dist/assets/dagre-VZM6K2ZE-h6IXMveB.js +0 -4
  30. package/dist/assets/diagram-7IWD3JNH-BCS0_bvz.js +0 -30
  31. package/dist/assets/diagram-B4RE2ZJO-HD8yOGXS.js +0 -3
  32. package/dist/assets/diagram-LBJQPF4R-D0UTsV2t.js +0 -24
  33. package/dist/assets/diagram-Q27KOJAE-Bmb3LIFV.js +0 -24
  34. package/dist/assets/diagram-UB23O5K3-DM2d8yVM.js +0 -41
  35. package/dist/assets/disconnected-BuxohaUu.png +0 -0
  36. package/dist/assets/ebnfDiagram-BXEA7PRR-DGZG2WqV.js +0 -1
  37. package/dist/assets/echarts-BB8VCqSB.js +0 -73
  38. package/dist/assets/erDiagram-JOGREHBK-BmfqN4Ac.js +0 -85
  39. package/dist/assets/flowDiagram-UKHOOZJN-CJNXFf5I.js +0 -156
  40. package/dist/assets/ganttDiagram-PKOTCBZU-BxdjClye.js +0 -292
  41. package/dist/assets/gitGraphDiagram-DS77QQ5N-5YVrNwZn.js +0 -106
  42. package/dist/assets/graph-C9eacEi8.js +0 -1
  43. package/dist/assets/index-C-4W3slC.css +0 -13
  44. package/dist/assets/index-gXtuwxvQ.js +0 -124
  45. package/dist/assets/infoDiagram-6WML65LV-CTv9fO4U.js +0 -2
  46. package/dist/assets/ishikawaDiagram-WSZJBQD7-8cPVb_uM.js +0 -70
  47. package/dist/assets/journeyDiagram-NVQOT4AX-Djlzn218.js +0 -139
  48. package/dist/assets/kanban-definition-27J2QSJJ-7xuKelm_.js +0 -89
  49. package/dist/assets/katex-C5jXJg4s.js +0 -257
  50. package/dist/assets/layout-DEXfKzaS.js +0 -1
  51. package/dist/assets/logo-DIAzbBuw.png +0 -0
  52. package/dist/assets/map-Czzmt4hB.js +0 -1
  53. package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  54. package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  55. package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  56. package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  57. package/dist/assets/mermaid-DUvIMwef.js +0 -314
  58. package/dist/assets/mindmap-definition-FAOFIHXS-tb_oy-Th.js +0 -96
  59. package/dist/assets/pegDiagram-VL7TDLO6-CVSX7i5k.js +0 -1
  60. package/dist/assets/pieDiagram-7S7Q4E2Y-D1szTwph.js +0 -39
  61. package/dist/assets/quadrantDiagram-CIZ2JOQS-CBHKJ-Lm.js +0 -7
  62. package/dist/assets/railroadDiagram-AXF67PYL-CtYWo-kB.js +0 -1
  63. package/dist/assets/requirementDiagram-LRYGKXZP-C_ClMADK.js +0 -84
  64. package/dist/assets/sankeyDiagram-W5VNT64P-uSc3f8Ek.js +0 -40
  65. package/dist/assets/sequenceDiagram-SI44F4Z6-DOatJfyp.js +0 -162
  66. package/dist/assets/sizeCapture-X5ZJPWSS-BuwX5NSJ.js +0 -1
  67. package/dist/assets/stateDiagram-OKZ733FA-DOA9ImyT.js +0 -1
  68. package/dist/assets/stateDiagram-v2-UEYNNEHI-_iiqH1Kh.js +0 -1
  69. package/dist/assets/swimlanes-SLNWSIFB-pn0ZRURI.js +0 -2
  70. package/dist/assets/swimlanesDiagram-ULZ7WXOC-B1KWR-ew.js +0 -8
  71. package/dist/assets/timeline-definition-Z64GVDOM-BMuORx8j.js +0 -120
  72. package/dist/assets/vennDiagram-T6HMQDX7-CX3BUH0P.js +0 -34
  73. package/dist/assets/vue-vendor-Bmy9n80X.js +0 -49
  74. package/dist/assets/wardleyDiagram-T6FBY63Y-BQg29rBi.js +0 -78
  75. package/dist/assets/workbox-window.prod.es5-BBnX5xw4.js +0 -2
  76. package/dist/assets/xychartDiagram-ELKLHX3M-XASTxFDn.js +0 -7
  77. package/dist/favicon.ico +0 -0
  78. package/dist/favicon.svg +0 -482
  79. package/dist/index.html +0 -24
  80. package/dist/logo-512x512.png +0 -0
  81. package/dist/logo.svg +0 -482
  82. package/dist/maskable-icon-512x512.png +0 -0
  83. package/dist/pwa-192x192.png +0 -0
  84. package/dist/pwa-512x512.png +0 -0
  85. package/dist/pwa-64x64.png +0 -0
  86. package/dist/sw.js +0 -2
@@ -63,4 +63,13 @@
63
63
  </ul>
64
64
  </dd>
65
65
  </dl>
66
+ <dl class="message-properties">
67
+ <dt>
68
+ Notification Display Time
69
+ <span class="property-type">number</span>
70
+ </dt>
71
+ <dd>
72
+ Number of seconds the "Connected" notification is shown after the Dashboard reconnects. Set to 0 to keep it open until dismissed.
73
+ </dd>
74
+ </dl>
66
75
  </script>
@@ -0,0 +1,108 @@
1
+ <script type="text/javascript">
2
+ /**
3
+ * Registers a "vue-sfc" language with Node-RED's Monaco code editor, used by ui-template.
4
+ * Up front this only registers the language id and Monaco's HTML language service for it. The tokenizer,
5
+ * Vue/Vuetify providers and Vuetify data are loaded from resources/vue-editor the first time an editor uses it.
6
+ */
7
+ (function () {
8
+ const PLUGIN_ID = 'ui-template-vue-editor'
9
+ const LANGUAGE_ID = 'vue-sfc'
10
+ const MODULE = '@flowfuse/node-red-dashboard'
11
+
12
+ /**
13
+ * Monaco asks MonacoEnvironment.getWorkerUrl/getWorker for a language worker using the language id as the
14
+ * label. Node-RED only knows its built-in labels, so map ours onto the html worker that serves it.
15
+ * Done on first use, when Node-RED has already set up MonacoEnvironment.
16
+ */
17
+ function shimWorkerLabel () {
18
+ const env = window.MonacoEnvironment
19
+ if (!env || env.vueSfcWorkerShim) {
20
+ return
21
+ }
22
+ for (const fn of ['getWorkerUrl', 'getWorker']) {
23
+ if (typeof env[fn] === 'function') {
24
+ const original = env[fn]
25
+ env[fn] = function (moduleId, label) {
26
+ return original.call(this, moduleId, label === LANGUAGE_ID ? 'html' : label)
27
+ }
28
+ }
29
+ }
30
+ env.vueSfcWorkerShim = true
31
+ }
32
+
33
+ /** Newer Node-RED versions register HTML-like languages itself, including the worker mapping */
34
+ function nodeRedRegistersHtmlLanguages () {
35
+ return typeof RED.editor?.codeEditor?.monaco?.registerHTMLLanguage === 'function'
36
+ }
37
+
38
+ function registerHtmlLanguageService (monaco) {
39
+ const options = { data: { useDefaultDataProvider: true } }
40
+ if (nodeRedRegistersHtmlLanguages()) {
41
+ RED.editor.codeEditor.monaco.registerHTMLLanguage(LANGUAGE_ID, options)
42
+ return
43
+ }
44
+ // Monaco 0.55 moved monaco.languages.html to monaco.html
45
+ const html = monaco.html?.registerHTMLLanguageService ? monaco.html : monaco.languages.html
46
+ if (!html?.registerHTMLLanguageService) {
47
+ return
48
+ }
49
+ html.registerHTMLLanguageService(LANGUAGE_ID, Object.assign({}, html.htmlDefaults?.options, options))
50
+ }
51
+
52
+ /**
53
+ * Registers the language if Monaco is loaded. Safe to call more than once.
54
+ * @returns {boolean} true when the vue-sfc language is available
55
+ */
56
+ function ensure () {
57
+ const monaco = window.monaco
58
+ if (!monaco?.languages?.register) {
59
+ return false
60
+ }
61
+ if (monaco.languages.getLanguages().some(l => l.id === LANGUAGE_ID)) {
62
+ return true
63
+ }
64
+ monaco.languages.register({ id: LANGUAGE_ID, aliases: ['Vue', LANGUAGE_ID], extensions: ['.vue'] })
65
+ try {
66
+ registerHtmlLanguageService(monaco)
67
+ } catch (err) {
68
+ console.warn('Dashboard: HTML language service not available for Vue templates', err)
69
+ }
70
+
71
+ const version = RED.nodes.registry?.getModule?.(MODULE)?.version
72
+ const query = version ? '?v=' + encodeURIComponent(version) : ''
73
+ const base = new URL('resources/' + MODULE + '/vue-editor/', document.baseURI)
74
+ monaco.languages.onLanguage(LANGUAGE_ID, function () {
75
+ if (!nodeRedRegistersHtmlLanguages()) {
76
+ shimWorkerLabel()
77
+ }
78
+ import(new URL('vue-sfc.mjs' + query, base).href)
79
+ .then(m => m.activate(monaco, { dataUrl: new URL('vuetify.json' + query, base).href }))
80
+ .catch(err => console.warn('Dashboard: could not load the Vue editor support', err))
81
+ })
82
+ return true
83
+ }
84
+
85
+ /**
86
+ * nr-assistant before 0.21.0 only gives ui-template Expert completions and its CodeLens for html,
87
+ * so keep ui-template on html when an older nr-assistant is installed.
88
+ */
89
+ function assistantSupportsVueSfc () {
90
+ const assistant = RED.plugins.getPlugin('flowfuse-nr-assistant')
91
+ return !assistant || assistant.supportsVueSfc === true
92
+ }
93
+
94
+ /**
95
+ * Registers the language if needed. Safe to call more than once.
96
+ * @returns {boolean} true when ui-template should use the vue-sfc language
97
+ */
98
+ function useVueSfc () {
99
+ return RED.editor.codeEditor?.editor?.type === 'monaco' && assistantSupportsVueSfc() && ensure()
100
+ }
101
+
102
+ RED.plugins.registerPlugin(PLUGIN_ID, {
103
+ onadd: ensure,
104
+ ensure,
105
+ useVueSfc
106
+ })
107
+ })()
108
+ </script>
@@ -18,6 +18,9 @@
18
18
  "template": "Template",
19
19
  "expand": "Expand",
20
20
  "resend": "Reload last value on refresh."
21
+ },
22
+ "errors": {
23
+ "invalid-template": "Error in template code"
21
24
  }
22
25
  }
23
26
  }
@@ -149,6 +149,14 @@
149
149
  height: { value: 0 },
150
150
  head: { value: '' },
151
151
  format: { value: defaultTemplate },
152
+ // number of errors the code editor reported when the edit dialog was last saved (set in oneditsave)
153
+ noerr: {
154
+ value: 0,
155
+ // two parameters, so Node-RED shows the returned message (a one-parameter validator's result is coerced to a boolean)
156
+ validate: function (v, opt) {
157
+ return !v ? true : c_('errors.invalid-template')
158
+ }
159
+ },
152
160
  storeOutMessages: { value: true },
153
161
  passthru: { value: true },
154
162
  resendOnRefresh: { value: true },
@@ -183,6 +191,11 @@
183
191
  const that = this
184
192
  const $templateScope = $('#node-input-templateScope')
185
193
 
194
+ // Monaco gets Vue/Vuetify syntax and completions from the ui-template-vue-editor plugin (nodes/plugins/vue-editor.html).
195
+ // ACE, Monaco without the plugin, or an nr-assistant too old for vue-sfc keeps the HTML mode.
196
+ const vueEditor = RED.plugins.getPlugin('ui-template-vue-editor')
197
+ const htmlMode = vueEditor?.useVueSfc?.() ? 'ace/mode/vue-sfc' : 'ace/mode/html'
198
+
186
199
  // if this groups parent is a subflow template, set the node-config-input-width and node-config-input-height up
187
200
  // as typedInputs and hide the elementSizer (as it doesn't make sense for subflow templates)
188
201
  if (RED.nodes.subflow(this.z)) {
@@ -239,15 +252,15 @@
239
252
  break
240
253
  case 'widget:ui':
241
254
  $('#template-row-ui').show()
242
- that.editor.getSession().setMode('ace/mode/html')
255
+ that.editor.getSession().setMode(htmlMode)
243
256
  break
244
257
  case 'widget:page':
245
258
  $('#template-row-page').show()
246
- that.editor.getSession().setMode('ace/mode/html')
259
+ that.editor.getSession().setMode(htmlMode)
247
260
  break
248
261
  default:
249
262
  $('#template-row-group, #template-row-class').show()
250
- that.editor.getSession().setMode('ace/mode/html')
263
+ that.editor.getSession().setMode(htmlMode)
251
264
  break
252
265
  }
253
266
  resize.call(that)
@@ -255,7 +268,7 @@
255
268
 
256
269
  this.editor = RED.editor.createEditor({
257
270
  id: 'node-input-format-editor',
258
- mode: 'ace/mode/html',
271
+ mode: htmlMode,
259
272
  value: $('#node-input-format').val()
260
273
  })
261
274
 
@@ -263,7 +276,7 @@
263
276
  url: 'uitemplates', // where to get the data from
264
277
  type: 'ui-template', // the type of object the library is for
265
278
  editor: this.editor, // the field name the main text body goes to
266
- mode: 'ace/mode/html',
279
+ mode: htmlMode,
267
280
  fields: ['name']
268
281
  })
269
282
 
@@ -275,7 +288,7 @@
275
288
  e.preventDefault()
276
289
  const value = that.editor.getValue()
277
290
  RED.editor.editText({
278
- mode: $templateScope.val() === 'global' ? 'css' : 'html',
291
+ mode: $templateScope.val() === 'global' ? 'css' : htmlMode.replace('ace/mode/', ''),
279
292
  value,
280
293
  width: 'Infinity',
281
294
  cursor: that.editor.getCursorPosition(),
@@ -423,6 +436,7 @@
423
436
  <div class="form-row" style="margin-bottom:0px;">
424
437
  <label for="node-input-format" style="width:110px;"><i class="fa fa-copy"></i> <span data-i18n="ui-template.label.template"></span></label>
425
438
  <input type="hidden" id="node-input-format">
439
+ <input type="hidden" id="node-input-noerr">
426
440
  <button id="node-template-expand-editor" class="red-ui-button red-ui-button-small" style="float:right"><i class="fa fa-expand"></i></button>
427
441
  </div>
428
442
  <div class="form-row node-text-editor-row" style="display: block;">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowfuse/node-red-dashboard",
3
- "version": "1.33.0",
3
+ "version": "1.33.1-215bca8-202610211539.0",
4
4
  "description": "FlowFuse Dashboard - A collection of Node-RED nodes that provide functionality to build your own UI applications (inc. forms, buttons, charts).",
5
5
  "keywords": [
6
6
  "node-red"
@@ -31,15 +31,17 @@
31
31
  ],
32
32
  "files": [
33
33
  "dist/*",
34
- "nodes/*"
34
+ "nodes/*",
35
+ "resources/*"
35
36
  ],
36
37
  "scripts": {
37
- "build": "vite build",
38
- "build:dev": "cross-env NODE_ENV=development vite build --mode development",
38
+ "build": "npm run build:vue-editor && vite build",
39
+ "build:dev": "npm run build:vue-editor && cross-env NODE_ENV=development vite build --mode development",
40
+ "build:vue-editor": "node scripts/build-vue-editor-data.js",
39
41
  "cy:open": "cypress open",
40
42
  "cy:run": "cypress run",
41
43
  "cy:server": "node-red -u ./cypress/fixtures/user-dir/ -p 1881",
42
- "dev": "cross-env NODE_ENV=development vite build --mode development --watch",
44
+ "dev": "npm run build:vue-editor && cross-env NODE_ENV=development vite build --mode development --watch",
43
45
  "dev:prod": "vite build --watch",
44
46
  "docs:build": "vitepress build docs",
45
47
  "docs:dev": "vitepress dev docs",
@@ -99,7 +101,7 @@
99
101
  "medium-zoom": "^1.1.0",
100
102
  "mermaid": "^11.12.2",
101
103
  "mocha": "^12.0.1",
102
- "node-red": "^4.1.8",
104
+ "node-red": "^4.1.16",
103
105
  "node-red-node-test-helper": "^0.3.5",
104
106
  "npm-run-all": "^4.1.5",
105
107
  "sass": "^1.96.0",
@@ -108,9 +110,10 @@
108
110
  "should-sinon": "^0.0.6",
109
111
  "sinon": "^21.0.0",
110
112
  "socket.io-client": "^4.8.1",
113
+ "socket.io-parser": "~4.2.6",
111
114
  "sort-package-json": "^2.15.1",
112
115
  "svgo": "^4.0.0",
113
- "vite": "^5.4.17",
116
+ "vite": "^6.4.4",
114
117
  "vite-plugin-image-optimizer": "^2.0.2",
115
118
  "vite-plugin-pwa": "^1.0.0",
116
119
  "vitepress": "^1.0.2",
@@ -156,18 +159,23 @@
156
159
  "ui-event": "nodes/widgets/ui_event.js",
157
160
  "ui-control": "nodes/widgets/ui_control.js",
158
161
  "ui-progress": "nodes/widgets/ui_progress.js"
162
+ },
163
+ "plugins": {
164
+ "ui-template-vue-editor": "nodes/plugins/vue-editor.html"
159
165
  }
160
166
  },
161
167
  "overrides": {
162
- "node-red-admin@4.1.2": {
163
- "axios": "^1.13.2"
164
- },
165
168
  "nise@5.1.9": {
166
169
  "path-to-regexp": "6.3.0"
167
170
  },
168
171
  "esbuild": ">=0.25.0",
169
172
  "cross-spawn": "~7.0.5",
170
173
  "js-yaml": ">=5.4.1",
171
- "tar": ">=7.5.4"
174
+ "tar": ">=7.5.4",
175
+ "vitepress": {
176
+ ".": "$vitepress",
177
+ "vite": "$vite"
178
+ },
179
+ "npm@<11.21.0": "11.21.0"
172
180
  }
173
181
  }
@@ -0,0 +1,148 @@
1
+ /* eslint-env browser */
2
+ /* global RED */
3
+ /**
4
+ * JavaScript and CSS editing for the <script> and <style> blocks of a ui-template (`vue-sfc` language).
5
+ *
6
+ * Node-RED provides the machinery (RED.editor.codeEditor.monaco.registerEmbeddedLanguages, in newer
7
+ * Node-RED versions). This module only adds what is specific to Dashboard: the type of `this`. Dashboard runs a
8
+ * ui-template script in one of two ways (ui/src/widgets/ui-template/UITemplate.vue, ui/src/util/vue.acorn.js):
9
+ * - a script whose only statement is `export default { ... }` is a Vue component; Node-RED's hidden copy
10
+ * of it becomes `export default __uiTemplate({ ... })`, which types `this` in data, computed, methods,
11
+ * watch, mounted and unmounted
12
+ * - any other script runs inside `mounted`, so it is wrapped in a function whose `this` is the component
13
+ * Both see what Dashboard adds to the component: send(), submit(), msg, value, id, props, $socket, ...
14
+ */
15
+
16
+ const TYPES_PATH = 'file:///node_modules/@types/node-red-dashboard-ui-template/index.d.ts'
17
+
18
+ /** Type declarations for the ui-template component instance */
19
+ export const UI_TEMPLATE_TYPES = `
20
+ /** Members that Dashboard adds to every ui-template component */
21
+ interface UiTemplateContext {
22
+ /** The ID of this ui-template node, assigned by Node-RED */
23
+ readonly id: string
24
+ /** The configuration of this ui-template node */
25
+ readonly props: { name?: string, templateScope?: string, [key: string]: any }
26
+ /** The last message received by this ui-template node */
27
+ readonly msg: { payload?: any, topic?: string, [key: string]: any }
28
+ /** Get or set \`msg.payload\` of the last message */
29
+ value: any
30
+ /** Send a message to the Node-RED flow. A non-object value is sent as \`msg.payload\`. */
31
+ send(msg: any): void
32
+ /** Send an event to the Node-RED flow, with \`topic\` set to the event type */
33
+ submit(event: Event): void
34
+ /** The socket.io connection to Node-RED */
35
+ readonly $socket: {
36
+ on(event: string, listener: (...args: any[]) => void): any
37
+ off(event: string, listener?: (...args: any[]) => void): any
38
+ emit(event: string, ...args: any[]): any
39
+ [key: string]: any
40
+ }
41
+ /** Last message received by every widget, by widget ID */
42
+ readonly messages: Record<string, any>
43
+ /** Dashboard setup information */
44
+ readonly setup: any
45
+ readonly $dataTracker: any
46
+ /** The Dashboard Vuex store */
47
+ readonly $store: any
48
+ /** The Dashboard router */
49
+ readonly $router: any
50
+ /** The current route */
51
+ readonly $route: any
52
+ /** The Vuetify instance */
53
+ readonly $vuetify: any
54
+ /** The root DOM element of this component */
55
+ readonly $el: any
56
+ /** Elements and components registered with \`ref\` */
57
+ readonly $refs: Record<string, any>
58
+ readonly $data: Record<string, any>
59
+ readonly $parent: any
60
+ readonly $root: any
61
+ readonly $options: Record<string, any>
62
+ /** Trigger a custom event on this component */
63
+ $emit(event: string, ...args: any[]): void
64
+ /** Wait for the next DOM update */
65
+ $nextTick(fn?: () => void): Promise<void>
66
+ /** Watch a reactive property or getter */
67
+ $watch(source: string | (() => any), callback: (value: any, oldValue: any) => void, options?: { immediate?: boolean, deep?: boolean }): () => void
68
+ /** Force this component to re-render */
69
+ $forceUpdate(): void
70
+ }
71
+ type UiTemplateComputed<C> = { [K in keyof C]: C[K] extends (...args: any[]) => infer R ? R : C[K] extends { get(): infer G } ? G : never }
72
+ type UiTemplateInstance<D, C, M> = D & UiTemplateComputed<C> & M & UiTemplateContext
73
+ type UiTemplateWatcher = ((value: any, oldValue: any) => void) | { handler: (value: any, oldValue: any) => void, deep?: boolean, immediate?: boolean }
74
+ /** The options that a ui-template component supports */
75
+ declare function __uiTemplate<D = {}, C = {}, M = {}>(component: {
76
+ name?: string
77
+ data?: (this: UiTemplateContext) => D
78
+ computed?: C & ThisType<UiTemplateInstance<D, C, M>>
79
+ methods?: M & ThisType<UiTemplateInstance<D, C, M>>
80
+ // \`this\` in these comes from the ThisType below; typing it here as well stops TypeScript inferring D, C and M
81
+ watch?: Record<string, UiTemplateWatcher>
82
+ mounted?: () => void
83
+ unmounted?: () => void
84
+ /** Dashboard ignores other options (it logs a warning); allowed here so that one doesn't stop \`this\` being typed */
85
+ [option: string]: any
86
+ } & ThisType<UiTemplateInstance<D, C, M>>): void
87
+ `
88
+
89
+ // whitespace and comments, which Dashboard allows around a component (they aren't statements)
90
+ const GAP = String.raw`(?:\s|\/\/[^\n]*(?:\n|$)|\/\*(?:[^*]|\*(?!\/))*\*\/)*`
91
+
92
+ /**
93
+ * Where a ui-template script is a component: Dashboard treats it as one when its only statement is
94
+ * `export default { ... }`.
95
+ * @param {string} body - the script's code
96
+ * @returns {{ open: number, close: number } | null} offsets (in body) of the object's `{` and `}`
97
+ */
98
+ export function findComponent (body) {
99
+ const head = new RegExp(`^${GAP}export\\s+default\\s*(?=\\{)`).exec(body)
100
+ const tail = new RegExp(`\\}\\s*;?${GAP}$`).exec(body)
101
+ return head && tail ? { open: head[0].length, close: tail.index } : null
102
+ }
103
+
104
+ /**
105
+ * The `wrap` hook for registerEmbeddedLanguages: text to insert around a script so `this` has its type.
106
+ * @param {string} text - the ui-template
107
+ * @param {{start: number, end: number}} region - the script's code
108
+ * @param {number} index - the script's position in the template
109
+ * @returns {{at: number, text: string}[]} insertions at offsets in the ui-template
110
+ */
111
+ export function wrapScript (text, region, index) {
112
+ const component = findComponent(text.slice(region.start, region.end))
113
+ if (component) {
114
+ return [
115
+ { at: region.start + component.open, text: '__uiTemplate(' },
116
+ { at: region.start + component.close + 1, text: ')' }
117
+ ]
118
+ }
119
+ return [
120
+ { at: region.start, text: `/** @this {UiTemplateContext} */ function __uiTemplateScript${index} () {` },
121
+ { at: region.end, text: '\n}' }
122
+ ]
123
+ }
124
+
125
+ /**
126
+ * Registers Dashboard's script and style support with Node-RED's code editor.
127
+ * @param {object} monaco - the monaco namespace (unused, kept for the same signature as vue-sfc.mjs)
128
+ * @param {object} options
129
+ * @param {string} options.languageId - the ui-template language id (vue-sfc)
130
+ * @returns {boolean} false if this Node-RED doesn't provide registerEmbeddedLanguages
131
+ */
132
+ export function activate (monaco, { languageId }) {
133
+ const register = RED.editor?.codeEditor?.monaco?.registerEmbeddedLanguages
134
+ if (typeof register !== 'function') {
135
+ return false
136
+ }
137
+ register(languageId, {
138
+ javascript: {
139
+ wrap: wrapScript,
140
+ extraLibs: [{ content: UI_TEMPLATE_TYPES, filePath: TYPES_PATH }],
141
+ hiddenNames: /^__uiTemplate/,
142
+ // list the component's own members before the Vue/Dashboard `$` members
143
+ demotedNames: /^\$/
144
+ },
145
+ css: {}
146
+ })
147
+ return true
148
+ }