fit-ui 2.10.13 → 2.11.2

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/dist/Fit.UI.js CHANGED
@@ -682,7 +682,7 @@ Fit._internal =
682
682
  {
683
683
  Core:
684
684
  {
685
- VersionInfo: { Major: 2, Minor: 10, Patch: 13 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
685
+ VersionInfo: { Major: 2, Minor: 11, Patch: 2 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
686
686
  }
687
687
  };
688
688
 
@@ -2219,17 +2219,29 @@ for (var i in vals = [1,3,6,8])
2219
2219
  Fit.Browser = {};
2220
2220
  Fit._internal.Browser = {};
2221
2221
 
2222
+ // Allow Fit.Browser unit tests to override user agent information
2223
+ // to test parsing of a range of different user agent strings.
2224
+ Fit._internal.Browser.UserAgent = navigator.userAgent;
2225
+
2222
2226
  /// <function container="Fit.Browser" name="GetBrowser" access="public" static="true" returns='"Edge" | "Chrome" | "Safari" | "MSIE" | "Firefox" | "Opera" | "Unknown"'>
2223
2227
  /// <description>
2224
- /// Returns name of browser. Possible values are: Chrome (which also covers modern versions of Opera and Edge),
2225
- /// Safari, Edge (version 12-18), MSIE (version 8-11), Firefox, Opera (version 1-12), Unknown.
2228
+ /// Returns name of browser useful for adjusting behaviour based on render engine.
2229
+ /// Possible values are: Chrome (both WebKit and Blink based - also returned for modern versions of
2230
+ /// Opera and Edge), Safari (also returned for Edge, Chrome, Opera, and Firefox on iOS), Edge (version 12-18),
2231
+ /// MSIE (version 8-11), Firefox, Opera (version 1-12), and Unknown.
2226
2232
  /// </description>
2227
2233
  /// <param name="returnAppId" type="false" default="false"> Set True to have app specific identifier returned </param>
2228
2234
  /// </function>
2229
- /// <function container="Fit.Browser" name="GetBrowser" access="public" static="true" returns='"Edge" | "EdgeChromium" | "Chrome" | "Safari" | "MSIE" | "Firefox" | "Opera" | "OperaChromium" | "Unknown"'>
2235
+ /// <function container="Fit.Browser" name="GetBrowser" access="public" static="true" returns='"Edge" | "Chrome" | "Safari" | "MSIE" | "Firefox" | "Opera" | "Unknown"'>
2230
2236
  /// <description>
2231
- /// Returns browser app identifer. Possible values are: Chrome, Safari, Edge (version 12-18), EdgeChromium (version 85+),
2232
- /// MSIE (version 8-11), Firefox, Opera (version 1-12), OperaChromium (version 15+), Unknown
2237
+ /// Returns browser app name useful for adjusting behaviour based on actual
2238
+ /// application, regardless of render engine and platform. Possible values are:
2239
+ /// Chrome (both Webkit and Blink based), Safari, Edge (both legacy and modern),
2240
+ /// MSIE (version 8-11), Firefox, Opera (both legacy and modern), and Unknown.
2241
+ /// Be careful not to check against browser app name and app version alone.
2242
+ /// For instance Opera 3 on a touch device is newer than Opera 60 on a Desktop
2243
+ /// device, as they are two completely different browsers. Check whether the browser runs on
2244
+ /// a tablet or phone using e.g. Fit.Browser.IsMobile(true) or Fit.Browser.GetInfo(true).IsMobile.
2233
2245
  /// </description>
2234
2246
  /// <param name="returnAppId" type="true"> Set True to have app specific identifier returned </param>
2235
2247
  /// </function>
@@ -2237,7 +2249,7 @@ Fit.Browser.GetBrowser = function(returnAppId)
2237
2249
  {
2238
2250
  Fit.Validation.ExpectBoolean(returnAppId, true);
2239
2251
 
2240
- var agent = navigator.userAgent;
2252
+ var agent = Fit._internal.Browser.UserAgent;
2241
2253
 
2242
2254
  // IMPORTANT: The order in which browsers are detected matters! For instance, several browsers define both Chrome and Safari as part of their user agent string. Examples:
2243
2255
  // Firefox 46 "Mozilla/5.0 (Windows NT 5.2; rv:46.0) Gecko/20100101 Firefox/46.0"
@@ -2251,20 +2263,18 @@ Fit.Browser.GetBrowser = function(returnAppId)
2251
2263
  // Edge 18 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18363"
2252
2264
  // Edge 85 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.41"
2253
2265
 
2254
- if (agent.indexOf("Edge/") > -1)
2255
- return "Edge";
2256
- if (returnAppId === true && agent.indexOf("Edg/") > -1)
2257
- return "EdgeChromium";
2266
+ if (agent.indexOf("Edge/") > -1 || (returnAppId === true && (agent.indexOf("Edg/") > -1 || agent.indexOf("EdgiOS/") > -1 || agent.indexOf("EdgA/") > -1)))
2267
+ return "Edge"; // Legacy Edge is identified by "Edge/", Chromium based Edge is identified by "Edg/", Edge on iOS is identified by "EdgiOS/", and Edge on Android is identified by "EdgA/"
2258
2268
  if (agent.indexOf("MSIE") > -1 || agent.indexOf("Trident") > -1)
2259
2269
  return "MSIE";
2260
- if (agent.indexOf("Firefox") > -1)
2261
- return "Firefox";
2262
- if (agent.indexOf("Opera") > -1)
2263
- return "Opera";
2264
- if (returnAppId === true && agent.indexOf("OPR/") > -1)
2265
- return "OperaChromium";
2266
- if (agent.indexOf("Chrome") > -1)
2267
- return "Chrome";
2270
+ if (agent.indexOf("Opera") > -1 || (returnAppId === true && (agent.indexOf("OPR/") > -1 || agent.indexOf("OPT/") > -1)))
2271
+ return "Opera"; // Legacy Opera is identified by "Opera", Chromium based Opera is identified by "OPR/", and Opera on iOS is identified by "OPT/"
2272
+ if (agent.indexOf("OPT/") > -1 && (agent.indexOf("iPhone;") > -1 || agent.indexOf("iPad;") > -1))
2273
+ return "Safari"; // Opera on iOS does not contain "Opera", nor "Safari" to identify the engine like other browsers using WebView - we can use "OPT/" plus "iPhone;" or "iPad;" instead
2274
+ if (agent.indexOf("Firefox") > -1 || (returnAppId === true && agent.indexOf("FxiOS/") > -1))
2275
+ return "Firefox"; // Firefox is identified by "Firefox" except on iOS where "FxiOS/" identifies it instead
2276
+ if (agent.indexOf("Chrome") > -1 || (returnAppId === true && agent.indexOf("CriOS/") > -1))
2277
+ return "Chrome"; // Chrome is identified by "Chrome" except on iOS where "CriOS/" identifies it instead
2268
2278
  if (agent.indexOf("Safari") > -1)
2269
2279
  return "Safari";
2270
2280
 
@@ -2298,37 +2308,56 @@ Fit.Browser.GetVersion = function(returnAppVersion)
2298
2308
 
2299
2309
  var start = 0;
2300
2310
  var end = 0;
2301
- var agent = navigator.userAgent;
2311
+ var agent = Fit._internal.Browser.UserAgent;
2302
2312
 
2303
2313
  if (browser === "Edge")
2304
2314
  {
2305
- start = agent.indexOf("Edge/");
2306
- start = (start !== -1 ? start + 5 : 0);
2307
- end = agent.indexOf(".", start);
2308
- end = (end !== -1 ? end : 0);
2309
- }
2310
- if (browser === "EdgeChromium")
2311
- {
2312
- start = agent.indexOf("Edg/");
2313
- start = (start !== -1 ? start + 4 : 0);
2315
+ var search = agent.indexOf("Edge/") > -1 && "Edge/" || agent.indexOf("Edg/") > -1 && "Edg/" || agent.indexOf("EdgiOS/") > -1 && "EdgiOS/" || agent.indexOf("EdgA/") > -1 && "EdgA/" || null;
2316
+
2317
+ start = search !== null && agent.indexOf(search) || -1;
2318
+ start = (start !== -1 ? start + search.length : 0);
2314
2319
  end = agent.indexOf(".", start);
2315
2320
  end = (end !== -1 ? end : 0);
2316
2321
  }
2317
- if (browser === "Chrome")
2322
+ else if (browser === "Chrome")
2318
2323
  {
2319
- start = agent.indexOf("Chrome/");
2320
- start = (start !== -1 ? start + 7 : 0);
2324
+ var search = agent.indexOf("Chrome/") > -1 && "Chrome/" || agent.indexOf("CriOS/") > -1 && "CriOS/" || null;
2325
+
2326
+ start = search !== null && agent.indexOf(search) || -1;
2327
+ start = (start !== -1 ? start + search.length : 0);
2321
2328
  end = agent.indexOf(".", start);
2322
2329
  end = (end !== -1 ? end : 0);
2323
2330
  }
2324
- if (browser === "Safari")
2331
+ else if (browser === "Safari")
2325
2332
  {
2326
- start = agent.indexOf("Version/");
2327
- start = (start !== -1 ? start + 8 : 0);
2328
- end = agent.indexOf(".", start);
2329
- end = (end !== -1 ? end : 0);
2333
+ var search = agent.indexOf("CriOS/") > -1 && "CriOS/" || agent.indexOf("FxiOS/") > -1 && "FxiOS/" || agent.indexOf("EdgiOS/") > -1 && "EdgiOS/" || agent.indexOf("OPT/") > -1 && "OPT/" || null;
2334
+
2335
+ if (search !== null) // Browser based on WebView on iOS - Chrome, Firefox, Edge, or Opera
2336
+ {
2337
+ if (returnAppVersion !== true) // Return Safari version parsed from OS version
2338
+ {
2339
+ start = agent.indexOf(" OS ");
2340
+ start = (start !== -1 ? start + 4 : 0);
2341
+ end = agent.indexOf("_", start);
2342
+ end = (end !== -1 ? end : 0);
2343
+ }
2344
+ else // Return application version
2345
+ {
2346
+ start = agent.indexOf(search);
2347
+ start = (start !== -1 ? start + search.length : 0);
2348
+ end = agent.indexOf(".", start);
2349
+ end = (end !== -1 ? end : 0);
2350
+ }
2351
+ }
2352
+ else // Real Safari or Firefox on iPad which does not identify itself as Firefox
2353
+ {
2354
+ start = agent.indexOf("Version/");
2355
+ start = (start !== -1 ? start + 8 : 0);
2356
+ end = agent.indexOf(".", start);
2357
+ end = (end !== -1 ? end : 0);
2358
+ }
2330
2359
  }
2331
- if (browser === "MSIE")
2360
+ else if (browser === "MSIE")
2332
2361
  {
2333
2362
  if (agent.indexOf("MSIE") > -1)
2334
2363
  {
@@ -2345,37 +2374,21 @@ Fit.Browser.GetVersion = function(returnAppVersion)
2345
2374
  end = (end !== -1 ? end : 0);
2346
2375
  }
2347
2376
  }
2348
- if (browser === "Firefox")
2377
+ else if (browser === "Firefox")
2349
2378
  {
2350
- start = agent.indexOf("Firefox/");
2351
- start = (start !== -1 ? start + 8 : 0);
2352
- end = agent.indexOf(".", start);
2353
- end = (end !== -1 ? end : 0);
2354
- }
2355
- if (browser === "Opera")
2356
- {
2357
- start = agent.indexOf("Version/");
2358
- start = (start !== -1 ? start + 8 : -1);
2359
-
2360
- if (start === -1)
2361
- {
2362
- start = agent.indexOf("Opera/");
2363
- start = (start !== -1 ? start + 6 : -1);
2364
- }
2365
-
2366
- if (start === -1)
2367
- {
2368
- start = agent.indexOf("Opera ");
2369
- start = (start !== -1 ? start + 6 : -1);
2370
- }
2379
+ var search = agent.indexOf("Firefox/") > -1 && "Firefox/" || agent.indexOf("FxiOS/") > -1 && "FxiOS/" || null;
2371
2380
 
2381
+ start = search !== null && agent.indexOf(search) || -1;
2382
+ start = (start !== -1 ? start + search.length : 0);
2372
2383
  end = agent.indexOf(".", start);
2373
2384
  end = (end !== -1 ? end : 0);
2374
2385
  }
2375
- if (browser === "OperaChromium")
2386
+ else if (browser === "Opera")
2376
2387
  {
2377
- start = agent.indexOf("OPR/");
2378
- start = (start !== -1 ? start + 4 : 0);
2388
+ var search = agent.indexOf("Version/") > -1 && "Version/" || agent.indexOf("Opera ") > -1 && "Opera " || agent.indexOf("Opera/") > -1 && "Opera/" || agent.indexOf("OPR/") > -1 && "OPR/" || agent.indexOf("OPT/") > -1 && "OPT/" || null;
2389
+
2390
+ start = search !== null && agent.indexOf(search) || -1;
2391
+ start = (start !== -1 ? start + search.length : 0);
2379
2392
  end = agent.indexOf(".", start);
2380
2393
  end = (end !== -1 ? end : 0);
2381
2394
  }
@@ -2909,7 +2922,22 @@ Fit.Browser.GetScreenDimensions = function(onlyAvailable)
2909
2922
  }
2910
2923
 
2911
2924
  /// <function container="Fit.Browser" name="IsMobile" access="public" static="true" returns="boolean">
2912
- /// <description> Returns value indicating whether device is a mobile device or not </description>
2925
+ /// <description>
2926
+ /// Returns value indicating whether device is a mobile device or not.
2927
+ /// Notice that some phones and tablets may identify as desktop devices,
2928
+ /// in which case IsMobile(..) will return False. In this case consider
2929
+ /// using Fit.Browser.IsTouchEnabled(), e.g. in combination with
2930
+ /// Fit.Browser.GetBrowser(), or a check against the size of the viewport,
2931
+ /// which will provide some indication as to whether device should be treated
2932
+ /// as a mobile device or not. As an example, Safari on iPad identifies as
2933
+ /// a Mac computer by default (it has &quot;Request Desktop Website&quot; enabled by default).
2934
+ /// To always detect iPad and iPhone as a mobile device, no matter the configuration
2935
+ /// of &quot;Request Desktop Website&quot;), simply use:
2936
+ /// var isMobile = Fit.Browser.IsMobile(true) || (Fit.Browser.GetBrowser() === &quot;Safari&quot; &amp;&amp; Fit.Browser.IsTouchEnabled())
2937
+ /// We will not be able to distinguish between an iPhone and an iPad though, not even
2938
+ /// by looking at the viewport size, since this approach is unreliable with high resolution
2939
+ /// iPhones and support for split screen which affects the viewport size.
2940
+ /// </description>
2913
2941
  /// <param name="includeTablets" type="boolean" default="true"> Value indicating whether tablets are considered mobile devices or not </param>
2914
2942
  /// </function>
2915
2943
  Fit.Browser.IsMobile = function(includeTablets)
@@ -2919,7 +2947,7 @@ Fit.Browser.IsMobile = function(includeTablets)
2919
2947
  // Based on http://detectmobilebrowsers.com
2920
2948
  // See About section for Tablet support: http://detectmobilebrowsers.com/about
2921
2949
 
2922
- var nav = navigator.userAgent || navigator.vendor;
2950
+ var nav = Fit._internal.Browser.UserAgent;
2923
2951
 
2924
2952
  if (includeTablets !== false && /android|ipad|playbook|silk/i.test(nav))
2925
2953
  return true;
@@ -2935,7 +2963,6 @@ Fit.Browser.IsTouchEnabled = function()
2935
2963
  return ("ontouchstart" in window);
2936
2964
  }
2937
2965
 
2938
-
2939
2966
  /// <function container="Fit.Browser" name="Log" access="public" static="true">
2940
2967
  /// <description> Log message or object </description>
2941
2968
  /// <param name="msg" type="object"> Message or object to log </param>
@@ -8442,7 +8469,9 @@ Fit.Events.AddMutationObserver = function(elm, obs, deep)
8442
8469
  Fit.Events.AddHandler(document, "touchstart", Fit._internal.Events.CheckMutations);
8443
8470
  Fit.Events.AddHandler(document, "touchend", Fit._internal.Events.CheckMutations);
8444
8471
  Fit.Events.AddHandler(document, "touchcancel", Fit._internal.Events.CheckMutations);
8445
- Fit._internal.Events.MutationObserverIntervalId = setInterval(Fit._internal.Events.CheckMutations, 1000);
8472
+
8473
+ var checkInterval = Fit._internal.Events.Browser.Name !== "MSIE" ? 500 : 1000;
8474
+ Fit._internal.Events.MutationObserverIntervalId = setInterval(Fit._internal.Events.CheckMutations, checkInterval);
8446
8475
  }
8447
8476
 
8448
8477
  // Add mutation observer
@@ -22782,12 +22811,25 @@ Fit.Controls.Input = function(ctlId)
22782
22811
  }
22783
22812
  }
22784
22813
 
22814
+ // Guard against disposed control in case Focused(false) was called and an OnBlur handler disposed the control.
22815
+ // As the code further up shows, we call me._internal.FireOnBlur() if a dialog is currently open. This results
22816
+ // in OnBlur firing immediately, while normally it happens asynchronously due to how OnFocus and OnBlur is handled
22817
+ // in ControlBase, in which case we do not need to worry that the control might be disposed. It happens "later".
22818
+ // The situation can easily arise if an OnScroll handler is reponsible for removing focus from a control,
22819
+ // and if that control also has an OnBlur handler registered which disposes the control. Scrolling with a
22820
+ // dialog open will then trigger the situation which we guard against here.
22821
+ if (me === null)
22822
+ {
22823
+ return false;
22824
+ }
22825
+
22785
22826
  if (me.DesignMode() === true)
22786
22827
  {
22787
22828
  // If a dialog is open and it belongs to this control instance, and focus is found within dialog, then control is considered having focus.
22788
22829
  // However, if <body> is focused while dialog is open, control is also considered to have focus, since dialog temporarily assigns focus to
22789
22830
  // <body> when tabbing between elements within the dialog. This seems safe as no other control can be considered focused if <body> has focus.
22790
- if (Fit._internal.Controls.Input.ActiveEditorForDialog === me && (Fit.Dom.Contained(Fit._internal.Controls.Input.ActiveDialogForEditor.getElement().$, Fit.Dom.GetFocused()) === true || Fit.Dom.GetFocused() === document.body))
22831
+ // We also consider the control focused if an associated dialog (modal) is currently loading (Fit._internal.Controls.Input.ActiveDialogForEditor is null).
22832
+ if (Fit._internal.Controls.Input.ActiveEditorForDialog === me && (Fit._internal.Controls.Input.ActiveDialogForEditor === null || Fit.Dom.Contained(Fit._internal.Controls.Input.ActiveDialogForEditor.getElement().$, Fit.Dom.GetFocused()) === true || Fit.Dom.GetFocused() === document.body))
22791
22833
  return true;
22792
22834
 
22793
22835
  // If a toolbar dialog/callout is open and contains the element currently having focus, then control is considered having focus.
@@ -22834,12 +22876,48 @@ Fit.Controls.Input = function(ctlId)
22834
22876
  });
22835
22877
 
22836
22878
  updateDesignEditorPlaceholder();
22879
+ updateDesignEditorSize(); // In case auto grow is enabled, in which case editor must adjust its height to its new content
22837
22880
  }
22838
22881
  else
22839
22882
  {
22840
22883
  input.value = val;
22841
22884
  }
22842
22885
 
22886
+ // Notice: Identical logic is NOT found in DesignMode(true, config) as with RevokeExternalBlobUrlsOnDispose below.
22887
+ // When the RevokeUnreferencedBlobUrlsOnValueSet mechanism is in play, the control has already been used as an HTML editor
22888
+ // before, as we are expecting the control to be re(-used) to manipulate different values. In this case we already have
22889
+ // designEditorConfig available, although it could theoretically be changed over time so RevokeUnreferencedBlobUrlsOnValueSet
22890
+ // is sometimes enabled and sometimes not - but we don't care to support poor design like this:
22891
+ // input.DesignMode(true, configWithRevokeUnreferencedBlobUrlsOnValueSetDISBLED);
22892
+ // input.Value("New HTML value");
22893
+ // input.DesignMode(true, configWithRevokeUnreferencedBlobUrlsOnValueSetENABLED); // This will not clean up image blobs no longer referenced
22894
+ if (designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeUnreferencedBlobUrlsOnValueSet === true)
22895
+ {
22896
+ // Remove image blobs from memory when a new value is set, unless some (or all)
22897
+ // of these image blobs are still referenced in the new value, of course.
22898
+ // This is useful if an editor instance is being (re-)used to modify different values.
22899
+ // NOTICE: There is a major memory leak in CKEditor related to bulk pasting images
22900
+ // from the file system, and the last image pasted always remains in memory:
22901
+ // https://github.com/ckeditor/ckeditor4/issues/5124
22902
+
22903
+ var blobUrlsReferenced = Fit.String.ParseImageBlobUrls(val);
22904
+ var newImageBlobUrls = [];
22905
+
22906
+ Fit.Array.ForEach(imageBlobUrls, function(blobUrl)
22907
+ {
22908
+ if (Fit.Array.Contains(blobUrlsReferenced, blobUrl) === true)
22909
+ {
22910
+ newImageBlobUrls.push(blobUrl); // Keep - still referenced in new value
22911
+ }
22912
+ else
22913
+ {
22914
+ URL.revokeObjectURL(blobUrl); // Revoke - no longer referenced in new value
22915
+ }
22916
+ });
22917
+
22918
+ imageBlobUrls = newImageBlobUrls;
22919
+ }
22920
+
22843
22921
  // Notice: Identical logic found in DesignMode(true, config)!
22844
22922
  if (designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeExternalBlobUrlsOnDispose === true)
22845
22923
  {
@@ -22848,8 +22926,11 @@ Fit.Controls.Input = function(ctlId)
22848
22926
  // is allowed (and expected) to take control over memory management for these blobs
22849
22927
  // based on the rule set in RevokeBlobUrlsOnDispose.
22850
22928
  // This code is also found in DesignMode(true, config) since images might be added before
22851
- // editor is created, in which case we do not yet have the editor configuration used to determine
22852
- // the desired behaviour.
22929
+ // DesignMode is enabled, in which case we do not yet have the editor configuration needed
22930
+ // to determine the desired behaviour.
22931
+ // NOTICE: There is a major memory leak in CKEditor related to bulk pasting images
22932
+ // from the file system, and the last image pasted always remains in memory:
22933
+ // https://github.com/ckeditor/ckeditor4/issues/5124
22853
22934
 
22854
22935
  var blobUrls = Fit.String.ParseImageBlobUrls(val);
22855
22936
 
@@ -23598,6 +23679,12 @@ Fit.Controls.Input = function(ctlId)
23598
23679
  /// These images are furthermore subject to the rule set in RevokeBlobUrlsOnDispose.
23599
23680
  /// Defaults to False.
23600
23681
  /// </member>
23682
+ /// <member name="RevokeUnreferencedBlobUrlsOnValueSet" type="boolean" default="undefined">
23683
+ /// This option is in effect when EmbedType is blob.
23684
+ /// Dispose images from blob storage (revoke blob URLs) when value is changed with Value(..),
23685
+ /// but keep any images still referenced in new value. This is useful if an editor instance
23686
+ /// is being used to modify different HTML values over time.
23687
+ /// </member>
23601
23688
  /// </container>
23602
23689
 
23603
23690
  /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigPlugins">
@@ -25453,6 +25540,8 @@ Fit.Controls.Input = function(ctlId)
25453
25540
  {
25454
25541
  if (me.DesignMode() === true && designEditorHeightMonitorId === -1)
25455
25542
  {
25543
+ // Postpone if editor is not ready yet
25544
+
25456
25545
  if (designEditorUpdateSizeDebouncer !== -1)
25457
25546
  {
25458
25547
  clearTimeout(designEditorUpdateSizeDebouncer);
@@ -25468,7 +25557,7 @@ Fit.Controls.Input = function(ctlId)
25468
25557
  // This is a problem because CKEditor uses setTimeout(..) to for instance
25469
25558
  // allow early registration of events, and because resources are loaded
25470
25559
  // in an async. manner.
25471
- designEditorUpdateSizeDebouncer = setTimeout(function()
25560
+ designEditorUpdateSizeDebouncer = setTimeout(function() // Timer is stopped if control is disposed
25472
25561
  {
25473
25562
  designEditorUpdateSizeDebouncer = -1;
25474
25563
  updateDesignEditorSize();
@@ -25477,19 +25566,8 @@ Fit.Controls.Input = function(ctlId)
25477
25566
  return;
25478
25567
  }
25479
25568
 
25480
- //var w = me.Width();
25481
- var h = me.Height();
25482
-
25483
- // Default control width is 200px (defined in Styles.css).
25484
- // NOTICE: resize does not work reliably when editor is hidden, e.g. behind a tab with display:none.
25485
- // The height set will not have the height of the toolbar substracted since the height can not be
25486
- // determined for hidden objects, so the editor will become larger than the value set (height specified + toolbar height).
25487
- // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-resize
25488
- designEditorSuppressOnResize = true;
25489
- designEditor.resize("100%", h.Value > -1 ? h.Value + h.Unit : "100%"); // A height of 100% allow editor to automatically adjust the height of the editor's content area to the height of its content (data-autogrow="true" must be set to make control container adjust to its content as well)
25490
- designEditorSuppressOnResize = false;
25491
-
25492
- // Set mutation observer responsible for updating editor size once it becomes visible
25569
+ // Postpone update to editor size if control is currently hidden or not
25570
+ // rooted in DOM, in which case designEditor.resize(..) will throw an error.
25493
25571
 
25494
25572
  if (mutationObserverId !== -1) // Cancel any mutation observer previously registered
25495
25573
  {
@@ -25497,22 +25575,43 @@ Fit.Controls.Input = function(ctlId)
25497
25575
  mutationObserverId = -1;
25498
25576
  }
25499
25577
 
25500
- var concealer = Fit.Dom.GetConcealer(me.GetDomElement()); // Get element hiding editor
25501
-
25502
- if (concealer !== null) // Editor is hidden - adjust size when it becomes visible
25578
+ if (Fit.Dom.IsVisible(me.GetDomElement()) === false) // Hidden (e.g. display:none or not rooted in DOM)
25503
25579
  {
25504
- mutationObserverId = Fit.Events.AddMutationObserver(concealer, function(elm)
25580
+ // Mutation observer is triggered when element changes, including when rooted, in which case
25581
+ // width and height becomes measurable, and changes to dimensions also trigger mutation observer.
25582
+ mutationObserverId = Fit.Events.AddMutationObserver(me.GetDomElement(), function(elm)
25505
25583
  {
25506
25584
  if (Fit.Dom.IsVisible(me.GetDomElement()) === true)
25507
25585
  {
25508
- designEditorSuppressOnResize = true;
25509
- designEditor.resize("100%", h.Value > -1 ? h.Value + h.Unit : "100%"); // A height of 100% allow editor to automatically adjust the height of the editor's content area to the height of its content (data-autogrow="true" must be set to make control container adjust to its content as well)
25510
- designEditorSuppressOnResize = false;
25586
+ disconnect();
25587
+ mutationObserverId = -1;
25511
25588
 
25512
- disconnect(); // Observers are expensive - remove when no longer needed
25589
+ updateDesignEditorSize(); // Does nothing if DesignMode is no longer enabled
25513
25590
  }
25514
25591
  });
25592
+
25593
+ return;
25515
25594
  }
25595
+
25596
+ //var w = me.Width();
25597
+ var h = me.Height();
25598
+
25599
+ // If editor is configured with AutoGrow enabled and toolbar is configured with HideWhenInactive,
25600
+ // then editor won't be able to adjust its height when not focused, since a fixed height is applied
25601
+ // to the editable area while the toolbar is hidden. Therefore, temporarily show the toolbar, update
25602
+ // the editor size, and then hide the toolbar again.
25603
+ var showHideToolbar = me.Focused() === false;
25604
+
25605
+ // Default control width is 200px (defined in Styles.css).
25606
+ // NOTICE: resize does not work reliably when editor is hidden, e.g. behind a tab with display:none.
25607
+ // The height set will not have the height of the toolbar substracted since the height can not be
25608
+ // determined for hidden objects, so the editor will become larger than the value set (height specified + toolbar height).
25609
+ // http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-resize
25610
+ designEditorSuppressOnResize = true;
25611
+ showHideToolbar && restoreHiddenToolbarInDesignEditor(true); // Does nothing unless HideWhenInactive is enabled - true argument prevents call back to updateDesignEditorSize again, hence preventing a "maximum call stack exceeded" error
25612
+ designEditor.resize("100%", h.Value > -1 ? h.Value + h.Unit : "100%"); // A height of 100% allow editor to automatically adjust the height of the editor's content area to the height of its content (data-autogrow="true" must be set to make control container adjust to its content as well)
25613
+ showHideToolbar && hideToolbarInDesignMode(true); // Does nothing unless HideWhenInactive is enabled - true argument prevents call back to updateDesignEditorSize again, hence preventing a "maximum call stack exceeded" error
25614
+ designEditorSuppressOnResize = false;
25516
25615
  }
25517
25616
  }
25518
25617
 
@@ -25560,8 +25659,10 @@ Fit.Controls.Input = function(ctlId)
25560
25659
  return (toolbarContainer !== null && toolbarContainer.style.display === "none");
25561
25660
  }
25562
25661
 
25563
- function hideToolbarInDesignMode()
25662
+ function hideToolbarInDesignMode(suppressUpdateEditorSize)
25564
25663
  {
25664
+ Fit.Validation.ExpectBoolean(suppressUpdateEditorSize, true);
25665
+
25565
25666
  if (designModeEnabledAndReady() === true && designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.HideWhenInactive === true)
25566
25667
  {
25567
25668
  var toolbarContainer = designEditorDom.Top || designEditorDom.Bottom; // Top is null if editor is placed at the bottom
@@ -25593,12 +25694,14 @@ Fit.Controls.Input = function(ctlId)
25593
25694
  toolbarContainer.style.display = "none";
25594
25695
 
25595
25696
  // Make editable area adjust to take up space previously consumed by toolbar
25596
- updateSize === true && updateDesignEditorSize();
25697
+ updateSize === true && suppressUpdateEditorSize !== true && updateDesignEditorSize();
25597
25698
  }
25598
25699
  }
25599
25700
 
25600
- function restoreHiddenToolbarInDesignEditor()
25701
+ function restoreHiddenToolbarInDesignEditor(suppressUpdateEditorSize)
25601
25702
  {
25703
+ Fit.Validation.ExpectBoolean(suppressUpdateEditorSize, true);
25704
+
25602
25705
  if (designModeEnabledAndReady() === true && designEditorConfig !== null && designEditorConfig.Toolbar && designEditorConfig.Toolbar.HideWhenInactive === true)
25603
25706
  {
25604
25707
  // Toolbar has been initially hidden - make it appear again
@@ -25642,7 +25745,7 @@ Fit.Controls.Input = function(ctlId)
25642
25745
  // Update size of editable area in case auto grow is not enabled, in which case
25643
25746
  // toolbar will now have taken up space outside of control's container (overflowing).
25644
25747
  // Make editable area fit control container again.
25645
- updateDesignEditorSize();
25748
+ suppressUpdateEditorSize !== true && updateDesignEditorSize();
25646
25749
  }
25647
25750
  else
25648
25751
  {