@node-red/editor-client 4.1.0-beta.1 → 4.1.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.
package/public/red/red.js CHANGED
@@ -6341,6 +6341,7 @@ RED.nodes = (function() {
6341
6341
  * - id:replace - import over the top of existing
6342
6342
  * - modules: map of module:version - hints for unknown nodes
6343
6343
  * - applyNodeDefaults - whether to apply default values to the imported nodes (default: false)
6344
+ * - eventContext - context to include in the `nodes:add` event
6344
6345
  */
6345
6346
  function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) {
6346
6347
  const defOpts = {
@@ -6349,7 +6350,8 @@ RED.nodes = (function() {
6349
6350
  markChanged: false,
6350
6351
  reimport: false,
6351
6352
  importMap: {},
6352
- applyNodeDefaults: false
6353
+ applyNodeDefaults: false,
6354
+ eventContext: null
6353
6355
  }
6354
6356
  options = Object.assign({}, defOpts, options)
6355
6357
  options.importMap = options.importMap || {}
@@ -6501,29 +6503,29 @@ RED.nodes = (function() {
6501
6503
  // Provide option to install missing modules
6502
6504
  notificationOptions.buttons = [
6503
6505
  {
6504
- text: RED._("palette.editor.manageModules"),
6506
+ text: RED._("palette.editor.installAll"),
6505
6507
  class: "primary",
6506
6508
  click: function(e) {
6507
6509
  unknownNotification.close();
6508
6510
 
6509
6511
  RED.actions.invoke('core:manage-palette', {
6510
- view: 'install',
6511
- filter: '"' + missingModules.join('", "') + '"'
6512
+ autoInstall: true,
6513
+ modules: missingModules.reduce((modules, moduleName) => {
6514
+ modules[moduleName] = options.modules[moduleName];
6515
+ return modules;
6516
+ }, {}),
6512
6517
  });
6513
6518
  }
6514
6519
  },
6515
6520
  {
6516
- text: RED._("palette.editor.installAll"),
6521
+ text: RED._("palette.editor.manageModules"),
6517
6522
  class: "pull-left",
6518
6523
  click: function(e) {
6519
6524
  unknownNotification.close();
6520
6525
 
6521
6526
  RED.actions.invoke('core:manage-palette', {
6522
- autoInstall: true,
6523
- modules: missingModules.reduce((modules, moduleName) => {
6524
- modules[moduleName] = options.modules[moduleName];
6525
- return modules;
6526
- }, {}),
6527
+ view: 'install',
6528
+ filter: '"' + missingModules.join('", "') + '"'
6527
6529
  });
6528
6530
  }
6529
6531
  }
@@ -7225,7 +7227,7 @@ RED.nodes = (function() {
7225
7227
 
7226
7228
  // Now the nodes have been fully updated, add them.
7227
7229
  for (i=0;i<new_nodes.length;i++) {
7228
- new_nodes[i] = addNode(new_nodes[i])
7230
+ new_nodes[i] = addNode(new_nodes[i], options.eventContext)
7229
7231
  node_map[new_nodes[i].id] = new_nodes[i]
7230
7232
  }
7231
7233
 
@@ -9864,7 +9866,7 @@ RED.utils = (function() {
9864
9866
 
9865
9867
  renderer.code = function (code, lang) {
9866
9868
  if(lang === "mermaid") {
9867
- return `<pre class='mermaid'>${code}</pre>`;
9869
+ return `<pre style='word-break: unset;' data-c64='${btoa(code)}' class='mermaid'>${code}</pre>`;
9868
9870
  } else {
9869
9871
  return "<pre><code>" +code +"</code></pre>";
9870
9872
  }
@@ -17310,14 +17312,25 @@ RED.stack = (function() {
17310
17312
  * The function must either return auto-complete options, or pass them
17311
17313
  * to the optional 'done' parameter.
17312
17314
  * If the function signature includes 'done', it must be used
17315
+ * The auto-complete options can either be an array of strings, or an array of objects in the form:
17316
+ * {
17317
+ * value: String : the value to insert if selected
17318
+ * label: String|DOM Element : the label to display in the dropdown.
17319
+ * }
17320
+ *
17313
17321
  * minLength: number
17314
17322
  * If `minLength` is 0, pressing down arrow will show the list
17323
+ *
17324
+ * completionPluginType: String
17325
+ * If provided instead of `search`, this will look for any plugins
17326
+ * registered with the given type that implement the `getCompletions` function. This
17327
+ * can be an async function that returns an array of string completions. It does not support
17328
+ * the full options object as above.
17315
17329
  *
17316
- * The auto-complete options should be an array of objects in the form:
17317
- * {
17318
- * value: String : the value to insert if selected
17319
- * label: String|DOM Element : the label to display in the dropdown.
17320
- * }
17330
+ * node: Node
17331
+ * If provided, this will be passed to the `getCompletions` function of the plugin
17332
+ * to allow the plugin to provide context-aware completions.
17333
+ *
17321
17334
  *
17322
17335
  */
17323
17336
 
@@ -17326,6 +17339,54 @@ RED.stack = (function() {
17326
17339
  const that = this;
17327
17340
  this.completionMenuShown = false;
17328
17341
  this.options.minLength = parseInteger(this.options.minLength, 1, 0);
17342
+ if (!this.options.search) {
17343
+ // No search function provided; nothing to provide completions
17344
+ if (this.options.completionPluginType) {
17345
+ const plugins = RED.plugins.getPluginsByType(this.options.completionPluginType)
17346
+ if (plugins.length > 0) {
17347
+ this.options.search = async function (value, done) {
17348
+ // for now, only support a single plugin
17349
+ const promises = plugins.map(plugin => plugin.getCompletions(value, that.options.context))
17350
+ const completions = (await Promise.all(promises)).flat()
17351
+ const results = []
17352
+ completions.forEach(completion => {
17353
+ const element = $('<div>',{style: "display: flex"})
17354
+ const valEl = $('<div/>',{ class: "red-ui-autoComplete-completion" })
17355
+ const valMatch = getMatch(completion, value)
17356
+ if (valMatch.found) {
17357
+ valEl.append(generateSpans(valMatch))
17358
+ valEl.appendTo(element)
17359
+ results.push({
17360
+ value: completion,
17361
+ label: element,
17362
+ match: valMatch
17363
+ })
17364
+ }
17365
+ results.sort((a, b) => {
17366
+ if (a.match.exact && !b.match.exact) {
17367
+ return -1;
17368
+ } else if (!a.match.exact && b.match.exact) {
17369
+ return 1;
17370
+ } else if (a.match.index < b.match.index) {
17371
+ return -1;
17372
+ } else if (a.match.index > b.match.index) {
17373
+ return 1;
17374
+ } else {
17375
+ return 0;
17376
+ }
17377
+ })
17378
+ })
17379
+ done(results)
17380
+ }
17381
+ } else {
17382
+ // No search function and no plugins found
17383
+ return
17384
+ }
17385
+ } else {
17386
+ // No search function and no plugin type provided
17387
+ return
17388
+ }
17389
+ }
17329
17390
  this.options.search = this.options.search || function() { return [] };
17330
17391
  this.element.addClass("red-ui-autoComplete");
17331
17392
  this.element.on("keydown.red-ui-autoComplete", function(evt) {
@@ -17387,6 +17448,11 @@ RED.stack = (function() {
17387
17448
  }
17388
17449
  return
17389
17450
  }
17451
+ if (typeof completions[0] === "string") {
17452
+ completions = completions.map(function(c) {
17453
+ return { value: c, label: c };
17454
+ });
17455
+ }
17390
17456
  if (that.completionMenuShown) {
17391
17457
  that.menu.options(completions);
17392
17458
  } else {
@@ -17418,6 +17484,27 @@ RED.stack = (function() {
17418
17484
  if(isNaN(n) || n < min || n > max) { n = def || 0; }
17419
17485
  return n;
17420
17486
  }
17487
+ // TODO: this is copied from typedInput - should be a shared utility
17488
+ function getMatch(value, searchValue) {
17489
+ const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
17490
+ const len = idx > -1 ? searchValue.length : 0;
17491
+ return {
17492
+ index: idx,
17493
+ found: idx > -1,
17494
+ pre: value.substring(0,idx),
17495
+ match: value.substring(idx,idx+len),
17496
+ post: value.substring(idx+len),
17497
+ exact: idx === 0 && value.length === searchValue.length
17498
+ }
17499
+ }
17500
+ // TODO: this is copied from typedInput - should be a shared utility
17501
+ function generateSpans(match) {
17502
+ const els = [];
17503
+ if(match.pre) { els.push($('<span/>').text(match.pre)); }
17504
+ if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
17505
+ if(match.post) { els.push($('<span/>').text(match.post)); }
17506
+ return els;
17507
+ }
17421
17508
  })(jQuery);
17422
17509
  ;RED.actions = (function() {
17423
17510
  var actions = {
@@ -20657,6 +20744,7 @@ RED.keyboard = (function() {
20657
20744
  "backspace": 8,
20658
20745
  "delete": 46,
20659
20746
  "space": 32,
20747
+ "tab": 9,
20660
20748
  ";":186,
20661
20749
  "=":187,
20662
20750
  "+":187, // <- QWERTY specific
@@ -20923,23 +21011,27 @@ RED.keyboard = (function() {
20923
21011
  return resolveKeyEvent(evt);
20924
21012
  }
20925
21013
  }
20926
- d3.select(window).on("keydown",function() {
21014
+ d3.select(window).on("keydown", () => {
21015
+ handleEvent(d3.event)
21016
+ })
21017
+
21018
+ function handleEvent (evt) {
20927
21019
  if (!handlersActive) {
20928
21020
  return;
20929
21021
  }
20930
- if (metaKeyCodes[d3.event.keyCode]) {
21022
+ if (metaKeyCodes[evt]) {
20931
21023
  return;
20932
21024
  }
20933
- var handler = resolveKeyEvent(d3.event);
21025
+ var handler = resolveKeyEvent(evt);
20934
21026
  if (handler && handler.ondown) {
20935
21027
  if (typeof handler.ondown === "string") {
20936
21028
  RED.actions.invoke(handler.ondown);
20937
21029
  } else {
20938
21030
  handler.ondown();
20939
21031
  }
20940
- d3.event.preventDefault();
20941
- }
20942
- });
21032
+ evt.preventDefault();
21033
+ }
21034
+ }
20943
21035
 
20944
21036
  function addHandler(scope,key,modifiers,ondown) {
20945
21037
  var mod = modifiers;
@@ -21321,7 +21413,8 @@ RED.keyboard = (function() {
21321
21413
  formatKey: formatKey,
21322
21414
  validateKey: validateKey,
21323
21415
  disable: disable,
21324
- enable: enable
21416
+ enable: enable,
21417
+ handle: handleEvent
21325
21418
  }
21326
21419
 
21327
21420
  })();
@@ -23195,7 +23288,9 @@ RED.view = (function() {
23195
23288
  return;
23196
23289
  }
23197
23290
  var historyEvent = result.historyEvent;
23198
- var nn = RED.nodes.add(result.node, { source: 'palette' });
23291
+ const linkToSplice = $(ui.helper).data("splice");
23292
+
23293
+ var nn = RED.nodes.add(result.node, { source: 'palette', splice: !!linkToSplice });
23199
23294
 
23200
23295
  var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
23201
23296
  if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
@@ -23254,7 +23349,6 @@ RED.view = (function() {
23254
23349
  nn.y -= gridOffset.y;
23255
23350
  }
23256
23351
 
23257
- var linkToSplice = $(ui.helper).data("splice");
23258
23352
  if (linkToSplice) {
23259
23353
  spliceLink(linkToSplice, nn, historyEvent)
23260
23354
  }
@@ -23327,7 +23421,11 @@ RED.view = (function() {
23327
23421
  if (RED.workspaces.isLocked()) {
23328
23422
  return
23329
23423
  }
23330
- importNodes(clipboard,{generateIds: clipboardSource === 'copy', generateDefaultNames: clipboardSource === 'copy'});
23424
+ importNodes(clipboard, {
23425
+ generateIds: clipboardSource === 'copy',
23426
+ generateDefaultNames: clipboardSource === 'copy',
23427
+ eventContext: { source: 'clipboard' }
23428
+ });
23331
23429
  });
23332
23430
 
23333
23431
  RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
@@ -23937,10 +24035,12 @@ RED.view = (function() {
23937
24035
  var lastAddedX;
23938
24036
  var lastAddedWidth;
23939
24037
 
23940
- const context = {}
24038
+ const context = {
24039
+ workspace: RED.workspaces.active()
24040
+ }
23941
24041
 
23942
24042
  if (quickAddLink) {
23943
- context.source = quickAddLink.node.id;
24043
+ context.source = quickAddLink.node;
23944
24044
  context.sourcePort = quickAddLink.port;
23945
24045
  context.sourcePortType = quickAddLink.portType;
23946
24046
  if (quickAddLink?.virtualLink) {
@@ -23956,6 +24056,7 @@ RED.view = (function() {
23956
24056
  y:clientY-mainPos.top+ node_height/2 + 5 - (oy-point[1]),
23957
24057
  disableFocus: touchTrigger,
23958
24058
  filter: filter,
24059
+ context,
23959
24060
  move: function(dx,dy) {
23960
24061
  if (ghostNode) {
23961
24062
  var pos = d3.transform(ghostNode.attr("transform")).translate;
@@ -23996,7 +24097,8 @@ RED.view = (function() {
23996
24097
  touchImport: true,
23997
24098
  notify: false,
23998
24099
  // Ensure the node gets all of its defaults applied
23999
- applyNodeDefaults: true
24100
+ applyNodeDefaults: true,
24101
+ eventContext: { source: 'typeSearch' }
24000
24102
  })
24001
24103
  quickAddActive = false;
24002
24104
  ghostNode.remove();
@@ -24004,8 +24106,9 @@ RED.view = (function() {
24004
24106
  if (quickAddLink) {
24005
24107
  // Need to attach the link to the suggestion. This is assumed to be the first
24006
24108
  // node in the array - as that's the one we've focussed on.
24007
- const targetNode = importResult.nodeMap[type.nodes[0].id]
24008
-
24109
+ // We need to map from the suggested node's id to the imported node's id,
24110
+ // and then get the proxy object for the node
24111
+ const targetNode = RED.nodes.node(importResult.nodeMap[type.nodes[0].id].id)
24009
24112
  const drag_line = quickAddLink;
24010
24113
  let src = null, dst, src_port;
24011
24114
  if (drag_line.portType === PORT_TYPE_OUTPUT && (targetNode.inputs > 0 || drag_line.virtualLink) ) {
@@ -24045,6 +24148,7 @@ RED.view = (function() {
24045
24148
  }
24046
24149
  var nn;
24047
24150
  var historyEvent;
24151
+ let addHistoryEvent;
24048
24152
  if (/^_action_:/.test(type)) {
24049
24153
  const actionName = type.substring(9)
24050
24154
  quickAddActive = false;
@@ -24078,6 +24182,7 @@ RED.view = (function() {
24078
24182
  nn = result.node;
24079
24183
  historyEvent = result.historyEvent;
24080
24184
  }
24185
+ addHistoryEvent = historyEvent;
24081
24186
  if (keepAdding) {
24082
24187
  mouse_mode = RED.state.QUICK_JOINING;
24083
24188
  }
@@ -24091,7 +24196,7 @@ RED.view = (function() {
24091
24196
  if (nn.type === 'junction') {
24092
24197
  nn = RED.nodes.addJunction(nn);
24093
24198
  } else {
24094
- nn = RED.nodes.add(nn, { source: 'typeSearch' });
24199
+ nn = RED.nodes.add(nn, { source: 'typeSearch', splice: !!linkToSplice });
24095
24200
  }
24096
24201
  if (quickAddLink) {
24097
24202
  var drag_line = quickAddLink;
@@ -24232,7 +24337,8 @@ RED.view = (function() {
24232
24337
 
24233
24338
  if (linkToSplice) {
24234
24339
  resetMouseVars();
24235
- spliceLink(linkToSplice, nn, historyEvent)
24340
+ // Add any history event data to the original add event
24341
+ spliceLink(linkToSplice, nn, addHistoryEvent)
24236
24342
  }
24237
24343
  RED.history.push(historyEvent);
24238
24344
  RED.nodes.dirty(true);
@@ -24276,17 +24382,14 @@ RED.view = (function() {
24276
24382
  }
24277
24383
  },
24278
24384
  suggest: function (suggestion) {
24385
+ // TypeSearch only provides one suggestion at a time
24279
24386
  if (suggestion?.nodes?.length > 0) {
24280
24387
  // Reposition the suggestion relative to the existing ghost node position
24281
- const deltaX = suggestion.nodes[0].x - point[0]
24282
- const deltaY = suggestion.nodes[0].y - point[1]
24388
+ const deltaX = (suggestion.nodes[0].x || 0) - point[0]
24389
+ const deltaY = (suggestion.nodes[0].y || 0) - point[1]
24283
24390
  suggestion.nodes.forEach(node => {
24284
- if (Object.hasOwn(node, 'x')) {
24285
- node.x = node.x - deltaX
24286
- }
24287
- if (Object.hasOwn(node, 'y')) {
24288
- node.y = node.y - deltaY
24289
- }
24391
+ node.x = (node.x || 0) - deltaX
24392
+ node.y = (node.y || 0) - deltaY
24290
24393
  })
24291
24394
  }
24292
24395
  setSuggestedFlow(suggestion);
@@ -27297,6 +27400,10 @@ RED.view = (function() {
27297
27400
  .on("touchend",nodeTouchEnd)
27298
27401
  .on("mouseover",nodeMouseOver)
27299
27402
  .on("mouseout",nodeMouseOut);
27403
+ } else if (d.__ghostClick) {
27404
+ d3.select(mainRect)
27405
+ .on("mousedown",d.__ghostClick)
27406
+ .on("touchstart",d.__ghostClick)
27300
27407
  }
27301
27408
  nodeContents.appendChild(mainRect);
27302
27409
  //node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none");
@@ -27539,6 +27646,10 @@ RED.view = (function() {
27539
27646
  .on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
27540
27647
  .on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
27541
27648
  RED.hooks.trigger("viewAddPort",{node:d,el: this, port: inputGroup[0][0], portType: "input", portIndex: 0})
27649
+ } else if (d.__ghostClick) {
27650
+ inputGroupPorts
27651
+ .on("mousedown",d.__ghostClick)
27652
+ .on("touchstart",d.__ghostClick)
27542
27653
  }
27543
27654
  }
27544
27655
  var numOutputs = d.outputs;
@@ -27597,6 +27708,9 @@ RED.view = (function() {
27597
27708
  portPort.addEventListener("touchend", portTouchEndProxy);
27598
27709
  portPort.addEventListener("mouseover", portMouseOverProxy);
27599
27710
  portPort.addEventListener("mouseout", portMouseOutProxy);
27711
+ } else if (d.__ghostClick) {
27712
+ portPort.addEventListener("mousedown", d.__ghostClick)
27713
+ portPort.addEventListener("touchstart", d.__ghostClick)
27600
27714
  }
27601
27715
 
27602
27716
  this.appendChild(portGroup);
@@ -27898,6 +28012,10 @@ RED.view = (function() {
27898
28012
  }
27899
28013
  }
27900
28014
  })
28015
+ } else if (d.__ghostClick) {
28016
+ d3.select(pathBack)
28017
+ .on("mousedown",d.__ghostClick)
28018
+ .on("touchstart",d.__ghostClick)
27901
28019
  }
27902
28020
 
27903
28021
  var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path");
@@ -28369,7 +28487,8 @@ RED.view = (function() {
28369
28487
  generateIds: false,
28370
28488
  generateDefaultNames: false,
28371
28489
  notify: true,
28372
- applyNodeDefaults: false
28490
+ applyNodeDefaults: false,
28491
+ eventContext: null
28373
28492
  }
28374
28493
  const addNewFlow = options.addFlow
28375
28494
  const touchImport = options.touchImport;
@@ -28459,7 +28578,8 @@ RED.view = (function() {
28459
28578
  importMap: options.importMap,
28460
28579
  markChanged: true,
28461
28580
  modules: modules,
28462
- applyNodeDefaults: applyNodeDefaults
28581
+ applyNodeDefaults: applyNodeDefaults,
28582
+ eventContext: options.eventContext
28463
28583
  });
28464
28584
  if (importResult) {
28465
28585
  var new_nodes = importResult.nodes;
@@ -28914,10 +29034,10 @@ RED.view = (function() {
28914
29034
  nn.w = RED.view.node_width;
28915
29035
  nn.h = Math.max(RED.view.node_height, (nn.outputs || 0) * 15);
28916
29036
  nn.resize = true;
28917
- if (x != null && typeof x == "number" && x >= 0) {
29037
+ if (x != null && typeof x == "number") {
28918
29038
  nn.x = x;
28919
29039
  }
28920
- if (y != null && typeof y == "number" && y >= 0) {
29040
+ if (y != null && typeof y == "number") {
28921
29041
  nn.y = y;
28922
29042
  }
28923
29043
  var historyEvent = {
@@ -29007,7 +29127,9 @@ RED.view = (function() {
29007
29127
  * x: 0,
29008
29128
  * y: 0,
29009
29129
  * }
29010
- * ]
29130
+ * ],
29131
+ * "source": <sourceNode>,
29132
+ * "sourcePort": <sourcePort>,
29011
29133
  * }
29012
29134
  * If `nodes` is a single node without an id property, it will be generated
29013
29135
  * using its default properties.
@@ -29015,6 +29137,9 @@ RED.view = (function() {
29015
29137
  * If `nodes` has multiple, they must all have ids and will be assumed to be 'importable'.
29016
29138
  * In other words, a piece of valid flow json.
29017
29139
  *
29140
+ * `source`/`sourcePort` are option and used to indicate a node the suggestion should be connected to.
29141
+ * If provided, a ghost wire will be added between the source and the first node in the suggestion.
29142
+ *
29018
29143
  * Limitations:
29019
29144
  * - does not support groups, subflows or whole tabs
29020
29145
  * - does not support config nodes
@@ -29025,6 +29150,9 @@ RED.view = (function() {
29025
29150
  * @param {Object} suggestion - The suggestion object
29026
29151
  */
29027
29152
  function setSuggestedFlow (suggestion) {
29153
+ $(window).off('keydown.suggestedFlow')
29154
+ $(window).off('mousedown.suggestedFlow');
29155
+ RED.keyboard.enable()
29028
29156
  if (!currentSuggestion && !suggestion) {
29029
29157
  // Avoid unnecessary redraws
29030
29158
  return
@@ -29032,18 +29160,57 @@ RED.view = (function() {
29032
29160
  // Clear up any existing suggestion state
29033
29161
  clearSuggestedFlow()
29034
29162
  currentSuggestion = suggestion
29035
- if (suggestion?.nodes?.length > 0) {
29163
+ if (suggestion && suggestion.nodes) {
29164
+ // If suggestion.nodes is an array of arrays, then there are multiple suggestions being provided
29165
+ // Normalise the shape of the suggestion.nodes to be an array of arrays
29166
+ if (!Array.isArray(suggestion.nodes[0])) {
29167
+ suggestion.nodes = [suggestion.nodes]
29168
+ }
29169
+ suggestion.count = suggestion.nodes.length
29170
+ suggestion.currentIndex = 0
29171
+ suggestion.current = suggestion.nodes[suggestion.currentIndex]
29172
+ refreshSuggestedFlow()
29173
+ } else {
29174
+ redraw()
29175
+ }
29176
+ }
29177
+
29178
+ function refreshSuggestedFlow () {
29179
+ const suggestion = currentSuggestion
29180
+ suggestedNodes = []
29181
+ suggestedLinks = []
29182
+
29183
+ currentSuggestion.current = currentSuggestion.nodes[currentSuggestion.currentIndex]
29184
+ if (suggestion.current.length > 0) {
29036
29185
  const nodeMap = {}
29037
29186
  const links = []
29038
- suggestion.nodes.forEach(nodeConfig => {
29187
+ const positionOffset = { x: 0, y: 0 }
29188
+ if (suggestion.source && suggestion.position === 'relative') {
29189
+ // If the suggestion is relative to a source node, use its position plus a suitable offset
29190
+ let targetX = suggestion.source.x + (suggestion.source.w || 120) + (3 * gridSize)
29191
+ const targetY = suggestion.source.y
29192
+ // Keep targetY where it is, but ensure targetX is grid aligned
29193
+ if (snapGrid) {
29194
+ // This isn't a perfect grid snap, as we don't have the true node width at this point.
29195
+ // TODO: defer grid snapping until the node is created?
29196
+ const gridOffset = RED.view.tools.calculateGridSnapOffsets({ x: targetX, y: targetY, w: node_width, h: node_height });
29197
+ targetX += gridOffset.x
29198
+ }
29199
+
29200
+ positionOffset.x = targetX - (suggestion.current[0].x || 0)
29201
+ positionOffset.y = targetY - (suggestion.current[0].y || 0)
29202
+ }
29203
+
29204
+ suggestion.current.forEach(nodeConfig => {
29039
29205
  if (!nodeConfig.type || nodeConfig.type === 'group' || nodeConfig.type === 'subflow' || nodeConfig.type === 'tab') {
29040
29206
  // A node type we don't support previewing
29041
29207
  return
29042
29208
  }
29043
29209
 
29044
29210
  let node
29045
-
29046
29211
  if (nodeConfig.type === 'junction') {
29212
+ nodeConfig.x = (nodeConfig.x || 0) + positionOffset.x
29213
+ nodeConfig.y = (nodeConfig.y || 0) + positionOffset.y
29047
29214
  node = {
29048
29215
  _def: {defaults:{}},
29049
29216
  type: 'junction',
@@ -29064,6 +29231,8 @@ RED.view = (function() {
29064
29231
  // TODO: unknown node types could happen...
29065
29232
  return
29066
29233
  }
29234
+ nodeConfig.x = (nodeConfig.x || 0) + positionOffset.x
29235
+ nodeConfig.y = (nodeConfig.y || 0) + positionOffset.y
29067
29236
  const result = createNode(nodeConfig.type, nodeConfig.x, nodeConfig.y)
29068
29237
  if (!result) {
29069
29238
  return
@@ -29086,6 +29255,11 @@ RED.view = (function() {
29086
29255
  node.id = nodeConfig.id || node.id
29087
29256
  node.__ghost = true;
29088
29257
  node.dirty = true;
29258
+ if (suggestion.clickToApply) {
29259
+ node.__ghostClick = function () {
29260
+ applySuggestedFlow()
29261
+ }
29262
+ }
29089
29263
  nodeMap[node.id] = node
29090
29264
 
29091
29265
  if (nodeConfig.wires) {
@@ -29114,6 +29288,56 @@ RED.view = (function() {
29114
29288
  suggestedLinks.push(link)
29115
29289
  }
29116
29290
  })
29291
+ if (suggestion.source && suggestedNodes[0]?._def?.inputs > 0) {
29292
+ suggestedLinks.push({
29293
+ source: suggestion.source,
29294
+ sourcePort: suggestion.sourcePort || 0,
29295
+ target: suggestedNodes[0],
29296
+ targetPort: 0,
29297
+ __ghost: true
29298
+ })
29299
+ }
29300
+ if (!RED.typeSearch.isVisible()) {
29301
+ // Disable the core keyboard handling so we get priority.
29302
+ // Ideally we'd be able to do this via actions, but we can't currently scope
29303
+ // actions finely enough to only be handled when the suggested flow is active.
29304
+ RED.keyboard.disable()
29305
+ $(window).on('keydown.suggestedFlow', function (evt) {
29306
+ $(window).off('keydown.suggestedFlow')
29307
+ RED.keyboard.enable()
29308
+ if (evt.keyCode === 9) { // tab; apply suggestion
29309
+ evt.stopPropagation();
29310
+ evt.preventDefault();
29311
+ applySuggestedFlow();
29312
+ } else if (evt.keyCode === 38 && currentSuggestion.count > 1) { // up arrow
29313
+ evt.stopPropagation();
29314
+ evt.preventDefault();
29315
+ currentSuggestion.currentIndex--
29316
+ if (currentSuggestion.currentIndex < 0) {
29317
+ currentSuggestion.currentIndex = currentSuggestion.count - 1
29318
+ }
29319
+ refreshSuggestedFlow();
29320
+ } else if (evt.keyCode === 40 && currentSuggestion.count > 1) { // down arrow
29321
+ evt.stopPropagation();
29322
+ evt.preventDefault();
29323
+ currentSuggestion.currentIndex++
29324
+ if (currentSuggestion.currentIndex === currentSuggestion.count) {
29325
+ currentSuggestion.currentIndex = 0
29326
+ }
29327
+ refreshSuggestedFlow();
29328
+ } else { // Anything else; clear the suggestion
29329
+ clearSuggestedFlow();
29330
+ RED.view.redraw(true);
29331
+ // manually push the event to the keyboard handler
29332
+ RED.keyboard.handle(evt)
29333
+ }
29334
+ });
29335
+ }
29336
+ if (suggestion.clickToApply) {
29337
+ $(window).on('mousedown.suggestedFlow', function (evnt) {
29338
+ clearSuggestedFlow();
29339
+ })
29340
+ }
29117
29341
  }
29118
29342
  if (ghostNode) {
29119
29343
  if (suggestedNodes.length > 0) {
@@ -29122,26 +29346,62 @@ RED.view = (function() {
29122
29346
  ghostNode.style('opacity', 1)
29123
29347
  }
29124
29348
  }
29125
- redraw();
29349
+ if (currentSuggestion.count > 1 && suggestedNodes.length > 0) {
29350
+ suggestedNodes[0].status = {
29351
+ text: `${currentSuggestion.currentIndex + 1} / ${currentSuggestion.count}`,
29352
+ }
29353
+ suggestedNodes[0].dirtyStatus = true
29354
+ }
29355
+ redraw()
29126
29356
  }
29127
29357
 
29128
29358
  function clearSuggestedFlow () {
29359
+ $(window).off('mousedown.suggestedFlow');
29360
+ $(window).off('keydown.suggestedFlow')
29361
+ RED.keyboard.enable()
29129
29362
  currentSuggestion = null
29130
29363
  suggestedNodes = []
29131
29364
  suggestedLinks = []
29132
29365
  }
29133
29366
 
29134
29367
  function applySuggestedFlow () {
29135
- if (currentSuggestion && currentSuggestion.nodes) {
29136
- const nodesToImport = currentSuggestion.nodes
29368
+ if (currentSuggestion && currentSuggestion.current) {
29369
+ const nodesToImport = currentSuggestion.current
29370
+ const sourceNode = currentSuggestion.source
29371
+ const sourcePort = currentSuggestion.sourcePort || 0
29137
29372
  setSuggestedFlow(null)
29138
- return importNodes(nodesToImport, {
29373
+ const result = importNodes(nodesToImport, {
29139
29374
  generateIds: true,
29140
29375
  touchImport: true,
29141
29376
  notify: false,
29142
29377
  // Ensure the node gets all of its defaults applied
29143
- applyNodeDefaults: true
29378
+ applyNodeDefaults: true,
29379
+ eventContext: { source: 'suggestion' }
29144
29380
  })
29381
+ if (sourceNode) {
29382
+ const firstNode = result.nodeMap[nodesToImport[0].id]
29383
+ if (firstNode && firstNode._def?.inputs > 0) {
29384
+ // Connect the source node to the first node in the suggestion
29385
+ const link = {
29386
+ source: sourceNode,
29387
+ target: RED.nodes.node(firstNode.id),
29388
+ sourcePort: sourcePort,
29389
+ targetPort: 0
29390
+ };
29391
+ RED.nodes.addLink(link)
29392
+ let historyEvent = RED.history.peek();
29393
+ if (historyEvent.t === "multi") {
29394
+ historyEvent = historyEvent.events.find(e => e.t === "add")
29395
+ }
29396
+ if (historyEvent) {
29397
+ historyEvent.links = historyEvent.links || [];
29398
+ historyEvent.links.push(link);
29399
+ }
29400
+ RED.view.redraw(true);
29401
+ }
29402
+ }
29403
+
29404
+ return result
29145
29405
  }
29146
29406
  }
29147
29407
 
@@ -30121,6 +30381,8 @@ RED.view.tools = (function() {
30121
30381
 
30122
30382
 
30123
30383
  function gotoNearestNode(direction) {
30384
+ // Do not select a nearest node if move is active
30385
+ if (RED.view.state() === RED.state.MOVING_ACTIVE) { return }
30124
30386
  var selection = RED.view.selection();
30125
30387
  if (selection.nodes && selection.nodes.length === 1) {
30126
30388
  var origin = selection.nodes[0];
@@ -30900,8 +31162,8 @@ RED.view.tools = (function() {
30900
31162
  t: 'multi',
30901
31163
  events: historyEvents
30902
31164
  })
31165
+ RED.nodes.dirty(true)
30903
31166
  }
30904
- RED.nodes.dirty(true)
30905
31167
  RED.view.redraw()
30906
31168
  }
30907
31169
  }
@@ -31562,7 +31824,7 @@ RED.palette = (function() {
31562
31824
  getNodeCount: function (visibleOnly) {
31563
31825
  const nodes = catDiv.find(".red-ui-palette-node")
31564
31826
  if (visibleOnly) {
31565
- return nodes.filter(function() { return $(this).css('display') !== 'none'}).length
31827
+ return nodes.filter(function() { return $(this).attr("data-filter") !== "true"}).length
31566
31828
  } else {
31567
31829
  return nodes.length
31568
31830
  }
@@ -32054,8 +32316,10 @@ RED.palette = (function() {
32054
32316
  var currentLabel = $(el).attr("data-palette-label");
32055
32317
  var type = $(el).attr("data-palette-type");
32056
32318
  if (val === "" || re.test(type) || re.test(currentLabel)) {
32319
+ $(el).attr("data-filter", null)
32057
32320
  $(this).show();
32058
32321
  } else {
32322
+ $(el).attr("data-filter", "true")
32059
32323
  $(this).hide();
32060
32324
  }
32061
32325
  });
@@ -35691,6 +35955,11 @@ RED.palette.editor = (function() {
35691
35955
  });
35692
35956
 
35693
35957
  RED.events.on('registry:module-updated', function(ns) {
35958
+ if (nodeEntries[ns.module]) {
35959
+ // Set the node/plugin as updated
35960
+ nodeEntries[ns.module].info.pending_version = ns.version;
35961
+ }
35962
+
35694
35963
  refreshNodeModule(ns.module);
35695
35964
  refreshUpdateStatus();
35696
35965
  });
@@ -37827,6 +38096,7 @@ RED.editor = (function() {
37827
38096
 
37828
38097
  function showEditDialog(node, defaultTab) {
37829
38098
  if (buildingEditDialog) { return }
38099
+ if (editStack.includes(node)) { return }
37830
38100
  buildingEditDialog = true;
37831
38101
  if (node.z && RED.workspaces.isLocked(node.z)) { return }
37832
38102
  var editing_node = node;
@@ -38144,6 +38414,7 @@ RED.editor = (function() {
38144
38414
  var editing_config_node = RED.nodes.node(id);
38145
38415
  var activeEditPanes = [];
38146
38416
 
38417
+ if (editStack.includes(editing_config_node)) { return }
38147
38418
  if (editing_config_node && editing_config_node.z && RED.workspaces.isLocked(editing_config_node.z)) { return }
38148
38419
 
38149
38420
  var configNodeScope = ""; // default to global
@@ -38587,6 +38858,7 @@ RED.editor = (function() {
38587
38858
 
38588
38859
  function showEditSubflowDialog(subflow, defaultTab) {
38589
38860
  if (buildingEditDialog) { return }
38861
+ if (editStack.includes(subflow)) { return }
38590
38862
  buildingEditDialog = true;
38591
38863
 
38592
38864
  editStack.push(subflow);
@@ -38803,6 +39075,7 @@ RED.editor = (function() {
38803
39075
 
38804
39076
  function showEditGroupDialog(group, defaultTab) {
38805
39077
  if (buildingEditDialog) { return }
39078
+ if (editStack.includes(group)) { return }
38806
39079
  buildingEditDialog = true;
38807
39080
  if (group.z && RED.workspaces.isLocked(group.z)) { return }
38808
39081
  var editing_node = group;
@@ -38917,6 +39190,7 @@ RED.editor = (function() {
38917
39190
 
38918
39191
  function showEditFlowDialog(workspace, defaultTab) {
38919
39192
  if (buildingEditDialog) { return }
39193
+ if (editStack.includes(workspace)) { return }
38920
39194
  buildingEditDialog = true;
38921
39195
  var activeEditPanes = [];
38922
39196
  RED.view.state(RED.state.EDITING);
@@ -43208,7 +43482,7 @@ RED.editor = (function() {
43208
43482
 
43209
43483
  nodes.forEach(async node => {
43210
43484
  if (!node.getAttribute('mermaid-processed')) {
43211
- const mermaidContent = node.innerText
43485
+ const mermaidContent = atob($(node).data('c64'))
43212
43486
  node.setAttribute('mermaid-processed', true)
43213
43487
  try {
43214
43488
  const { svg } = await mermaid.render('mermaid-render-'+Date.now()+'-'+(diagramIds++), mermaidContent);
@@ -44949,6 +45223,15 @@ RED.editor.codeEditor.monaco = (function() {
44949
45223
  ed.gotoLine(row, col);
44950
45224
  }
44951
45225
  ed.type = type;
45226
+
45227
+ ed.onKeyDown((event) => {
45228
+ if (event.keyCode === monaco.KeyCode.Escape) {
45229
+ if (monacoWidgetsAreOpen(ed)) {
45230
+ event.preventDefault();
45231
+ }
45232
+ }
45233
+ })
45234
+
44952
45235
  return ed;
44953
45236
  }
44954
45237
 
@@ -44958,6 +45241,16 @@ RED.editor.codeEditor.monaco = (function() {
44958
45241
  return true;
44959
45242
  }
44960
45243
 
45244
+ function monacoWidgetsAreOpen(editor) {
45245
+ /** @type {HTMLElement} */
45246
+ const editorDomNode = editor.getDomNode()
45247
+ const suggestVisible = !!editorDomNode.querySelector('.monaco-editor .suggest-widget.visible');
45248
+ const parameterHintsVisible = !!editorDomNode.querySelector('.monaco-editor .parameter-hints-widget.visible');
45249
+ const findWidgetVisible = !!editorDomNode.querySelector('.monaco-editor .find-widget.visible');
45250
+ const renameInputVisible = !!editorDomNode.querySelector('.monaco-editor .rename-box');
45251
+ return suggestVisible || parameterHintsVisible || findWidgetVisible || renameInputVisible
45252
+ }
45253
+
44961
45254
  return {
44962
45255
  /**
44963
45256
  * Editor type
@@ -45000,11 +45293,14 @@ RED.editor.codeEditor.monaco = (function() {
45000
45293
  **/
45001
45294
  RED.eventLog = (function() {
45002
45295
 
45003
- var template = '<script type="text/x-red" data-template-name="_eventLog"><div class="form-row node-text-editor-row"><div style="height: 100%;min-height: 150px;" class="node-text-editor" id="red-ui-event-log-editor"></div></div></script>';
45296
+ const template = '<script type="text/x-red" data-template-name="_eventLog"><div class="form-row node-text-editor-row"><div style="height: 100%;min-height: 150px;" class="node-text-editor" id="red-ui-event-log-editor"></div></div></script>';
45297
+
45298
+ let eventLogEditor;
45299
+ let backlog = [];
45300
+ let shown = false;
45301
+
45302
+ const activeLogs = new Set()
45004
45303
 
45005
- var eventLogEditor;
45006
- var backlog = [];
45007
- var shown = false;
45008
45304
 
45009
45305
  function appendLogLine(line) {
45010
45306
  backlog.push(line);
@@ -45023,6 +45319,18 @@ RED.eventLog = (function() {
45023
45319
  init: function() {
45024
45320
  $(template).appendTo("#red-ui-editor-node-configs");
45025
45321
  RED.actions.add("core:show-event-log",RED.eventLog.show);
45322
+
45323
+ const statusWidget = $('<button type="button" class="red-ui-footer-button red-ui-event-log-status" style="line-height: normal"><img style="width: 80%" src="red/images/spin.svg"/></div></button>');
45324
+ statusWidget.on("click", function(evt) {
45325
+ RED.actions.invoke("core:show-event-log");
45326
+ })
45327
+ RED.statusBar.add({
45328
+ id: "red-ui-event-log-status",
45329
+ align: "right",
45330
+ element: statusWidget
45331
+ });
45332
+ RED.statusBar.hide("red-ui-event-log-status");
45333
+
45026
45334
  },
45027
45335
  show: function() {
45028
45336
  if (shown) {
@@ -45083,6 +45391,12 @@ RED.eventLog = (function() {
45083
45391
  },
45084
45392
  log: function(id,payload) {
45085
45393
  var ts = (new Date(payload.ts)).toISOString()+" ";
45394
+ if (!payload.end) {
45395
+ activeLogs.add(id)
45396
+ } else {
45397
+ activeLogs.delete(id);
45398
+ }
45399
+
45086
45400
  if (payload.type) {
45087
45401
  ts += "["+payload.type+"] "
45088
45402
  }
@@ -45096,6 +45410,11 @@ RED.eventLog = (function() {
45096
45410
  appendLogLine(ts+line);
45097
45411
  })
45098
45412
  }
45413
+ if (activeLogs.size > 0) {
45414
+ RED.statusBar.show("red-ui-event-log-status");
45415
+ } else {
45416
+ RED.statusBar.hide("red-ui-event-log-status");
45417
+ }
45099
45418
  },
45100
45419
  startEvent: function(name) {
45101
45420
  backlog.push("");
@@ -45337,14 +45656,6 @@ RED.eventLog = (function() {
45337
45656
  editorStack = $("#red-ui-editor-stack");
45338
45657
  $(window).on("resize", handleWindowResize);
45339
45658
  RED.events.on("sidebar:resize",handleWindowResize);
45340
- $("#red-ui-editor-shade").on("click", function() {
45341
- if (!openingTray) {
45342
- var tray = stack[stack.length-1];
45343
- if (tray && tray.primaryButton) {
45344
- tray.primaryButton.click();
45345
- }
45346
- }
45347
- });
45348
45659
  },
45349
45660
  show: function show(options) {
45350
45661
  lowerTrayZ();
@@ -49422,7 +49733,6 @@ RED.actionList = (function() {
49422
49733
  addItem: function(container, i, nodeItem) {
49423
49734
  // nodeItem can take multiple forms
49424
49735
  // - A node type: {type: "inject", def: RED.nodes.getType("inject"), label: "Inject"}
49425
- // - A flow suggestion: { suggestion: true, nodes: [] }
49426
49736
  // - A placeholder suggestion: { suggestionPlaceholder: true, label: 'loading suggestions...' }
49427
49737
 
49428
49738
  let nodeDef = nodeItem.def;
@@ -49502,7 +49812,17 @@ RED.actionList = (function() {
49502
49812
 
49503
49813
  }
49504
49814
 
49815
+ let activeSuggestion
49505
49816
  function updateSuggestion(nodeItem) {
49817
+ if (nodeItem === activeSuggestion) {
49818
+ return
49819
+ }
49820
+ if (!visible && nodeItem) {
49821
+ // Do not update suggestion if the dialog is not visible
49822
+ // - for example, whilst the dialog is closing and the user mouses over a new item
49823
+ return
49824
+ }
49825
+ activeSuggestion = nodeItem
49506
49826
  if (suggestCallback) {
49507
49827
  if (!nodeItem) {
49508
49828
  suggestCallback(null);
@@ -49524,6 +49844,14 @@ RED.actionList = (function() {
49524
49844
  }
49525
49845
  }
49526
49846
  function confirm(def) {
49847
+ if (!activeSuggestion) {
49848
+ // The user has hit Enter without selecting an entry in the list.
49849
+ // This means no suggestion has been shown yet - and the position recalculation
49850
+ // has not been done.
49851
+ // Trigger an update of the suggestion to get the position right before
49852
+ // applying.
49853
+ updateSuggestion(def)
49854
+ }
49527
49855
  hide();
49528
49856
  if (!def.nodes && !/^_action_:/.test(def.type)) {
49529
49857
  typesUsed[def.type] = Date.now();
@@ -49559,6 +49887,7 @@ RED.actionList = (function() {
49559
49887
  }
49560
49888
  visible = true;
49561
49889
  } else {
49890
+ updateSuggestion(null)
49562
49891
  dialog.hide();
49563
49892
  searchResultsDiv.hide();
49564
49893
  }
@@ -49599,9 +49928,7 @@ RED.actionList = (function() {
49599
49928
  },200);
49600
49929
  }
49601
49930
  function hide(fast) {
49602
- if (suggestCallback) {
49603
- suggestCallback(null);
49604
- }
49931
+ updateSuggestion(null)
49605
49932
  if (visible) {
49606
49933
  visible = false;
49607
49934
  if (dialog !== null) {
@@ -49700,32 +50027,37 @@ RED.actionList = (function() {
49700
50027
 
49701
50028
  let index = 0;
49702
50029
 
49703
- // const suggestionItem = {
49704
- // suggestionPlaceholder: true,
49705
- // label: 'loading suggestions...',
49706
- // separator: true,
49707
- // i: index++
49708
- // }
49709
- // searchResults.editableList('addItem', suggestionItem);
49710
- // setTimeout(function() {
49711
- // searchResults.editableList('removeItem', suggestionItem);
49712
-
49713
- // const suggestedItem = {
49714
- // suggestion: true,
49715
- // label: 'Change/Debug Combo',
49716
- // separator: true,
49717
- // i: suggestionItem.i,
49718
- // nodes: [
49719
- // { id: 'suggestion-1', type: 'change', x: 0, y: 0, wires:[['suggestion-2']] },
49720
- // { id: 'suggestion-2', type: 'function', outputs: 3, x: 200, y: 0, wires:[['suggestion-3'],['suggestion-4'],['suggestion-6']] },
49721
- // { id: 'suggestion-3', _g: 'suggestion-group-1', type: 'debug', x: 375, y: -40 },
49722
- // { id: 'suggestion-4', _g: 'suggestion-group-1', type: 'debug', x: 375, y: 0 },
49723
- // { id: 'suggestion-5', _g: 'suggestion-group-1', type: 'debug', x: 410, y: 40 },
49724
- // { id: 'suggestion-6', type: 'junction', wires: [['suggestion-5']], x:325, y:40 }
49725
- // ]
49726
- // }
49727
- // searchResults.editableList('addItem', suggestedItem);
49728
- // }, 1000)
50030
+ if (!opts.context?.virtualLink) {
50031
+ // Check for suggestion plugins
50032
+ const suggestionPlugins = RED.plugins.getPluginsByType('node-red-flow-suggestion-source');
50033
+ if (suggestionPlugins.length > 0) {
50034
+ const suggestionItem = {
50035
+ suggestionPlaceholder: true,
50036
+ label: RED._('palette.loadingSuggestions'),
50037
+ separator: true,
50038
+ i: index++
50039
+ }
50040
+ searchResults.editableList('addItem', suggestionItem);
50041
+ suggestionPlugins[0].getSuggestions(opts.context).then(function (suggestedFlows) {
50042
+ searchResults.editableList('removeItem', suggestionItem);
50043
+ if (!Array.isArray(suggestedFlows)) {
50044
+ suggestedFlows = [suggestedFlows];
50045
+ }
50046
+ suggestedFlows.forEach(function(suggestion, index) {
50047
+ const suggestedItem = {
50048
+ suggestion: true,
50049
+ separator: index === suggestedFlows.length - 1,
50050
+ i: suggestionItem.i,
50051
+ ...suggestion
50052
+ }
50053
+ if (!suggestion.label && suggestion.nodes && suggestion.nodes.length === 1 && suggestion.nodes[0].type) {
50054
+ suggestedItem.label = getTypeLabel(suggestion.nodes[0].type, RED.nodes.getType(suggestion.nodes[0].type));
50055
+ }
50056
+ searchResults.editableList('addItem', suggestedItem);
50057
+ })
50058
+ })
50059
+ }
50060
+ }
49729
50061
 
49730
50062
  for(i=0;i<common.length;i++) {
49731
50063
  let itemDef
@@ -49776,9 +50108,10 @@ RED.actionList = (function() {
49776
50108
  }
49777
50109
 
49778
50110
  return {
49779
- show: show,
50111
+ show,
49780
50112
  refresh: refreshTypeList,
49781
- hide: hide
50113
+ hide,
50114
+ isVisible: () => visible
49782
50115
  };
49783
50116
 
49784
50117
  })();