fit-ui 3.4.4 → 3.5.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/dist/Fit.UI.css CHANGED
@@ -5082,6 +5082,55 @@ div.FitUiControlInput[data-designmode="true"][data-toolbar="false"] [contentedit
5082
5082
  cursor: pointer;
5083
5083
  }
5084
5084
 
5085
+ /* Character counter callout */
5086
+
5087
+ div.FitUiControlInput[data-maxlength="true"] > span[data-maxlength]
5088
+ {
5089
+ display: none;
5090
+ }
5091
+ div.FitUiControlInput[data-maxlength="true"][data-focused="true"] > span[data-maxlength]
5092
+ {
5093
+ display: inline;
5094
+ cursor: default;
5095
+ position: absolute;
5096
+ top: 0em;
5097
+ right: 0;
5098
+ transform: translateX(calc(100% + 1em));
5099
+ font-size: 0.8em;
5100
+ line-height: 2.5em;
5101
+ padding: 0em 0.5em;
5102
+ background-color: #696969;
5103
+ color: white;
5104
+ border-radius: 0.5em;
5105
+ }
5106
+ div.FitUiControlInput[data-maxlength="true"][data-focused="true"][data-designmode="true"] > span[data-maxlength] /* Align with toolbar */
5107
+ {
5108
+ top: 0.6em;
5109
+ }
5110
+ div.FitUiControlInput[data-maxlength="true"][data-focused="true"] > span[data-maxlength]:before /* counter text, e.g. 33/100 */
5111
+ {
5112
+ content: attr(data-maxlength);
5113
+ }
5114
+ div.FitUiControlInput[data-maxlength="true"][data-focused="true"] > span[data-maxlength]:after /* Left side arrow */
5115
+ {
5116
+ content: "";
5117
+ position: absolute;
5118
+ top: 50%;
5119
+ right: calc(100% - 0.1em);
5120
+ transform: translateY(-50%);
5121
+ border-width: 0.6em;
5122
+ border-style: solid;
5123
+ border-color: transparent #696969 transparent transparent;
5124
+ }
5125
+ div.FitUiControlInput[data-maxlength="true"][data-focused="true"][data-valid="false"] > span[data-maxlength]
5126
+ {
5127
+ background-color: red;
5128
+ }
5129
+ div.FitUiControlInput[data-maxlength="true"][data-focused="true"][data-valid="false"] > span[data-maxlength]:after
5130
+ {
5131
+ border-color: transparent red transparent transparent;
5132
+ }
5133
+
5085
5134
  /* Fluent UI compatibility */
5086
5135
 
5087
5136
  /* The following elements are mounted in document root - make sure they stay on
package/dist/Fit.UI.js CHANGED
@@ -682,7 +682,7 @@ Fit._internal =
682
682
  {
683
683
  Core:
684
684
  {
685
- VersionInfo: { Major: 3, Minor: 4, Patch: 4 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
685
+ VersionInfo: { Major: 3, Minor: 5, Patch: 0 } // 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
 
@@ -24168,6 +24168,10 @@ Fit.Controls.Input = function(ctlId)
24168
24168
  var orgVal = ""; // Holds initial value used to determine IsDirty state
24169
24169
  var preVal = ""; // Holds latest change made by user - used to determine whether OnChange needs to be fired
24170
24170
  var input = null;
24171
+ var charCounter = null;
24172
+ var charCounterConfig = null;
24173
+ var maxLength = -1;
24174
+ var curLength = -1; // Holds input length - HTML formatting is excluded in Design Mode as it represents characters entered by the user
24171
24175
  var changeObserverId = -1; // Holds interval ID to observer function for controls (e.g. color picker) not continuously firing OnChange when value is changed
24172
24176
  var cmdResize = null;
24173
24177
  var designEditor = null;
@@ -24224,8 +24228,11 @@ Fit.Controls.Input = function(ctlId)
24224
24228
  input.type = "text";
24225
24229
  input.autocomplete = "off";
24226
24230
  input.spellcheck = true;
24227
- input.onkeyup = function()
24231
+ input.oninput/*onkeyup*/ = function()
24228
24232
  {
24233
+ curLength = -1; // Let getInputLength() calculate new length when called
24234
+ updateCharCounter();
24235
+
24229
24236
  if (designEditorClearPlaceholder === true)
24230
24237
  {
24231
24238
  designEditorClearPlaceholder = false; // Prevent additional calls to updateDesignEditorPlaceholder - it retrieves editor value which is expensive
@@ -24260,7 +24267,7 @@ Fit.Controls.Input = function(ctlId)
24260
24267
  }
24261
24268
  }
24262
24269
  }
24263
- input.onchange = function() // OnKeyUp does not catch changes by mouse (e.g. paste or moving selected text)
24270
+ /*input.onchange = function() // OnKeyUp does not catch changes by mouse (e.g. paste or moving selected text)
24264
24271
  {
24265
24272
  if (me === null)
24266
24273
  {
@@ -24271,11 +24278,12 @@ Fit.Controls.Input = function(ctlId)
24271
24278
  }
24272
24279
 
24273
24280
  input.onkeyup();
24274
- }
24281
+ }*/
24275
24282
  me._internal.AddDomElement(input);
24276
24283
 
24277
24284
  me.AddCssClass("FitUiControlInput");
24278
24285
 
24286
+ me._internal.Data("maxlength", "false");
24279
24287
  me._internal.Data("multiline", "false");
24280
24288
  me._internal.Data("maximizable", "false");
24281
24289
  me._internal.Data("maximized", "false");
@@ -24312,7 +24320,7 @@ Fit.Controls.Input = function(ctlId)
24312
24320
  {
24313
24321
  changeObserverId = setInterval(function()
24314
24322
  {
24315
- input.onkeyup();
24323
+ input.oninput(); //input.onkeyup();
24316
24324
  }, 100);
24317
24325
  }
24318
24326
  });
@@ -24371,6 +24379,42 @@ Fit.Controls.Input = function(ctlId)
24371
24379
  nativeResizableAvailable = window.MouseEvent && new MouseEvent("mousemove", {}).buttons !== undefined || false;
24372
24380
  }
24373
24381
  catch (err) {}
24382
+
24383
+ // MaxLength support
24384
+
24385
+ me.AddValidationRule(function(sender)
24386
+ {
24387
+ if (maxLength > 0 && getInputLength() > maxLength)
24388
+ {
24389
+ return charCounterConfig.ShowValidationError === true ? locale.MaxLengthExceeded.replace("{0}", maxLength + "") : false;
24390
+ }
24391
+ });
24392
+
24393
+ Fit.Events.AddHandler(me.GetDomElement(), "keypress", function(e)
24394
+ {
24395
+ var ev = Fit.Events.GetEvent(e);
24396
+
24397
+ // Suppress key stroke if max length is reached in HTML editor.
24398
+ // This is not perfect. Line breaks and emojis are not intercepted, neither is paste
24399
+ // operations, but such input will get caught as invalid if it exceeds maxlength when
24400
+ // focus is lost (see OnBlur handler below) or on the next key stroke. Poor man's implementation.
24401
+ if (maxLength > 0 && designEditor !== null && charCounterConfig.StopInputOnLimit === true && ev.keyCode !== 8 && ev.keyCode !== 46 && ev.ctrlKey === false && ev.altKey === false && ev.metaKey === false && getInputLength() >= maxLength && window.getSelection().toString() === "")
24402
+ {
24403
+ Fit.Events.PreventDefault(ev);
24404
+ }
24405
+ });
24406
+
24407
+ me.OnBlur(function(sender)
24408
+ {
24409
+ // HTML editor does not fire OnChange when inserting emojis. Make sure
24410
+ // counter and valid state is properly reflected when focus is lost.
24411
+ if (maxLength > 0 && designEditor !== null)
24412
+ {
24413
+ curLength = -1; // Let getInputLength() calculate new length when called
24414
+ updateCharCounter();
24415
+ me._internal.Validate();
24416
+ }
24417
+ });
24374
24418
  }
24375
24419
 
24376
24420
  // ============================================
@@ -24683,7 +24727,11 @@ Fit.Controls.Input = function(ctlId)
24683
24727
  }
24684
24728
 
24685
24729
  if (fireOnChange === true)
24730
+ {
24731
+ curLength = -1; // Let getInputLength() calculate new length when called
24732
+ updateCharCounter();
24686
24733
  me._internal.FireOnChange();
24734
+ }
24687
24735
  }
24688
24736
 
24689
24737
  if (designModeEnabledAndReady() === true)
@@ -24892,7 +24940,7 @@ Fit.Controls.Input = function(ctlId)
24892
24940
  });
24893
24941
  }
24894
24942
 
24895
- me = orgVal = preVal = input = changeObserverId = cmdResize = designEditor = designEditorDom = designEditorDirty = designEditorDirtyPending = designEditorConfig = designEditorReloadConfig = designEditorRestoreButtonState = designEditorSuppressPaste = designEditorSuppressOnResize = designEditorMustReloadWhenReady = designEditorMustDisposeWhenReady = designEditorUpdateSizeDebouncer = designEditorHeightMonitorId = designEditorActiveToolbarPanel = designEditorDetached = designEditorClearPlaceholder = designEditorCleanEditableDom = designEditorGlobalKeyDownEventId = designEditorGlobalKeyUpEventId /*= htmlWrappedInParagraph*/ = wasAutoChangedToMultiLineMode = minimizeHeight = maximizeHeight = minMaxUnit = maximizeHeightConfigured = resizable = nativeResizableAvailable = mutationObserverId = rootedEventId = createWhenReadyIntervalId = isIe8 = debounceOnChangeTimeout = debouncedOnChange = imageBlobUrls = locale = null;
24943
+ me = orgVal = preVal = input = charCounter = charCounterConfig = maxLength = curLength = changeObserverId = cmdResize = designEditor = designEditorDom = designEditorDirty = designEditorDirtyPending = designEditorConfig = designEditorReloadConfig = designEditorRestoreButtonState = designEditorSuppressPaste = designEditorSuppressOnResize = designEditorMustReloadWhenReady = designEditorMustDisposeWhenReady = designEditorUpdateSizeDebouncer = designEditorHeightMonitorId = designEditorActiveToolbarPanel = designEditorDetached = designEditorClearPlaceholder = designEditorCleanEditableDom = designEditorGlobalKeyDownEventId = designEditorGlobalKeyUpEventId /*= htmlWrappedInParagraph*/ = wasAutoChangedToMultiLineMode = minimizeHeight = maximizeHeight = minMaxUnit = maximizeHeightConfigured = resizable = nativeResizableAvailable = mutationObserverId = rootedEventId = createWhenReadyIntervalId = isIe8 = debounceOnChangeTimeout = debouncedOnChange = imageBlobUrls = locale = null;
24896
24944
 
24897
24945
  base();
24898
24946
  });
@@ -24985,6 +25033,91 @@ Fit.Controls.Input = function(ctlId)
24985
25033
  // Public
24986
25034
  // ============================================
24987
25035
 
25036
+ /// <container name="Fit.Controls.InputTypeDefs.MaxLengthConfig">
25037
+ /// <description> Configuration for MaxLength feature </description>
25038
+ /// <member name="HideUntilCharCount" type="integer" default="0">
25039
+ /// Hide character counter until this number of characters is reached
25040
+ /// </member>
25041
+ /// <member name="StopInputOnLimit" type="boolean" default="true">
25042
+ /// If True, blocks additional input when maximum text length is reached
25043
+ /// </member>
25044
+ /// <member name="ShowValidationError" type="boolean" default="false">
25045
+ /// If True, shows a validation error message when the maximum text lenght is reached
25046
+ /// </member>
25047
+ /// </container>
25048
+
25049
+ /// <function container="Fit.Controls.Input" name="MaxLength" access="public" returns="integer">
25050
+ /// <description>
25051
+ /// Get/set the maximum text length. Be aware that HTML formatting in Design Mode
25052
+ /// does not count - use AddValidationRule(..) to set a hard limit if needed.
25053
+ /// Returns -1 when no maximum text length is imposed.
25054
+ /// </description>
25055
+ /// <param name="val" type="integer" default="undefined"> If defined, value is set as maximum text length </param>
25056
+ /// <param name="config" type="Fit.Controls.InputTypeDefs.MaxLengthConfig" default="undefined">
25057
+ /// Optional configuration for how maximum text length is handled and behaves.
25058
+ /// </param>
25059
+ /// </function>
25060
+ this.MaxLength = function(val, config)
25061
+ {
25062
+ Fit.Validation.ExpectInteger(val, true);
25063
+ Fit.Validation.ExpectObject(config, true);
25064
+ Fit.Validation.ExpectInteger((config || {}).HideUntilCharCount, true);
25065
+ Fit.Validation.ExpectBoolean((config || {}).StopInputOnLimit, true);
25066
+ Fit.Validation.ExpectBoolean((config || {}).ShowValidationError, true);
25067
+
25068
+ if (Fit.Validation.IsSet(val) === true)
25069
+ {
25070
+ if (val > 0)
25071
+ {
25072
+ charCounterConfig =
25073
+ {
25074
+ HideUntilCharCount: config && config.HideUntilCharCount || 0,
25075
+ StopInputOnLimit: config ? config.StopInputOnLimit !== false : true,
25076
+ ShowValidationError: config ? config.ShowValidationError === true : false
25077
+ };
25078
+
25079
+ maxLength = val;
25080
+ me._internal.Data("maxlength", "true");
25081
+
25082
+ if (charCounter === null)
25083
+ {
25084
+ charCounter = document.createElement("span");
25085
+ charCounter.title = locale.MaxLengthExceeded.replace("{0}", maxLength + "");
25086
+ me._internal.AddDomElement(charCounter);
25087
+ }
25088
+
25089
+ if (charCounterConfig.HideUntilCharCount > 0)
25090
+ {
25091
+ charCounter.style.display = "none"; // Updated when updateCharCounter() is called
25092
+ }
25093
+
25094
+ if (charCounterConfig.StopInputOnLimit === true)
25095
+ {
25096
+ Fit.Dom.Attribute(input, "maxlength", maxLength + ""); // Prevents user from entering too many characters
25097
+ }
25098
+
25099
+ updateCharCounter();
25100
+ me._internal.Validate();
25101
+ }
25102
+ else if (val <= 0 && charCounter !== null)
25103
+ {
25104
+ charCounterConfig = null;
25105
+
25106
+ maxLength = -1;
25107
+ me._internal.Data("maxlength", "false");
25108
+
25109
+ me._internal.RemoveDomElement(charCounter);
25110
+ charCounter = null;
25111
+
25112
+ Fit.Dom.Attribute(input, "maxlength", null);
25113
+
25114
+ me._internal.Validate();
25115
+ }
25116
+ }
25117
+
25118
+ return maxLength;
25119
+ }
25120
+
24988
25121
  /// <function container="Fit.Controls.Input" name="Placeholder" access="public" returns="string">
24989
25122
  /// <description> Get/set value used as a placeholder to indicate expected input on supported browsers </description>
24990
25123
  /// <param name="val" type="string" default="undefined"> If defined, value is set as placeholder </param>
@@ -25129,16 +25262,18 @@ Fit.Controls.Input = function(ctlId)
25129
25262
  var focused = me.Focused();
25130
25263
 
25131
25264
  var oldInput = input;
25132
- me._internal.RemoveDomElement(oldInput);
25133
25265
 
25134
25266
  input = document.createElement("textarea");
25135
25267
  input.value = oldInput.value;
25136
25268
  input.spellcheck = oldInput.spellcheck;
25137
25269
  input.placeholder = oldInput.placeholder;
25138
25270
  input.disabled = oldInput.disabled;
25139
- input.onkeyup = oldInput.onkeyup;
25140
- input.onchange = oldInput.onchange;
25141
- me._internal.AddDomElement(input);
25271
+ //input.onkeyup = oldInput.onkeyup;
25272
+ //input.onchange = oldInput.onchange;
25273
+ input.oninput = oldInput.oninput;
25274
+
25275
+ Fit.Dom.Attribute(input, "maxlength", charCounterConfig && charCounterConfig.StopInputOnLimit === true ? maxLength + "" : null);
25276
+ Fit.Dom.Replace(oldInput, input);
25142
25277
 
25143
25278
  if (nativeResizableAvailable === true)
25144
25279
  {
@@ -25169,7 +25304,6 @@ Fit.Controls.Input = function(ctlId)
25169
25304
  var focused = me.Focused();
25170
25305
 
25171
25306
  var oldInput = input;
25172
- me._internal.RemoveDomElement(oldInput);
25173
25307
 
25174
25308
  if (cmdResize !== null)
25175
25309
  {
@@ -25194,9 +25328,12 @@ Fit.Controls.Input = function(ctlId)
25194
25328
  input.spellcheck = oldInput.spellcheck;
25195
25329
  input.placeholder = oldInput.placeholder;
25196
25330
  input.disabled = oldInput.disabled;
25197
- input.onkeyup = oldInput.onkeyup;
25198
- input.onchange = oldInput.onchange;
25199
- me._internal.AddDomElement(input);
25331
+ //input.onkeyup = oldInput.onkeyup;
25332
+ //input.onchange = oldInput.onchange;
25333
+ input.oninput = oldInput.oninput;
25334
+
25335
+ Fit.Dom.Attribute(input, "maxlength", charCounterConfig && charCounterConfig.StopInputOnLimit === true ? maxLength + "" : null);
25336
+ Fit.Dom.Replace(oldInput, input);
25200
25337
 
25201
25338
  me.Height(-1);
25202
25339
 
@@ -26235,6 +26372,45 @@ Fit.Controls.Input = function(ctlId)
26235
26372
  // Private
26236
26373
  // ============================================
26237
26374
 
26375
+ function updateCharCounter()
26376
+ {
26377
+ if (charCounter !== null)
26378
+ {
26379
+ var currentLength = getInputLength();
26380
+ Fit.Dom.Data(charCounter, "maxlength", currentLength + "/" + maxLength);
26381
+
26382
+ if (charCounter.style.display === "none") // Hidden if HideUntilCharCount is enabled
26383
+ {
26384
+ var showCounter = currentLength >= charCounterConfig.HideUntilCharCount;
26385
+
26386
+ if (showCounter === true) // Only go from hidden to shown, not the other way around - it might confuse the user if it comes and goes
26387
+ {
26388
+ charCounter.style.display = "";
26389
+ }
26390
+ }
26391
+ }
26392
+ }
26393
+
26394
+ function getInputLength()
26395
+ {
26396
+ if (curLength === -1)
26397
+ {
26398
+ if (designEditor !== null)
26399
+ {
26400
+ //return Fit.String.StripHtml(me.Value()).length;
26401
+ var span = document.createElement("span");
26402
+ span.innerHTML = me.Value();
26403
+ curLength = span.textContent.length; // Discards formatting and transforms HTML/HEX entities into characters
26404
+ }
26405
+ else
26406
+ {
26407
+ curLength = me.Value().length;
26408
+ }
26409
+ }
26410
+
26411
+ return curLength;
26412
+ }
26413
+
26238
26414
  function createEditor()
26239
26415
  {
26240
26416
  // NOTICE: CKEDITOR requires input control to be rooted in DOM.
@@ -27094,7 +27270,7 @@ Fit.Controls.Input = function(ctlId)
27094
27270
  designEditorDirty = true;
27095
27271
  }
27096
27272
 
27097
- input.onkeyup();
27273
+ input.oninput(); //input.onkeyup();
27098
27274
  },
27099
27275
  resize: function() // Fires when size is changed (except via auto grow), not just when resized using resize handle in lower right cornor
27100
27276
  {
@@ -28361,6 +28537,12 @@ Fit.Controls.Input = function(ctlId)
28361
28537
  designEditorDetached = detachedEditor;
28362
28538
  }
28363
28539
  }
28540
+
28541
+ if (charCounter !== null)
28542
+ {
28543
+ charCounter.title = locale.MaxLengthExceeded.replace("{0}", maxLength + "");
28544
+ me._internal.Validate();
28545
+ }
28364
28546
  }
28365
28547
 
28366
28548
  function repaint()
@@ -28486,6 +28668,8 @@ Fit.Internationalization.AddLocalization(Fit.Controls.Input,
28486
28668
 
28487
28669
  "en":
28488
28670
  {
28671
+ "MaxLengthExceeded": "A maximum of {0} characters allowed",
28672
+
28489
28673
  "Detach": "Open in dialog",
28490
28674
  "Ok": "OK",
28491
28675
  "Cancel": "Cancel",
@@ -28508,6 +28692,8 @@ Fit.Internationalization.AddLocalization(Fit.Controls.Input,
28508
28692
  },
28509
28693
  "da":
28510
28694
  {
28695
+ "MaxLengthExceeded": "Højest {0} tegn tilladt",
28696
+
28511
28697
  "Detach": "Åben i dialog",
28512
28698
  "Ok": "OK",
28513
28699
  "Cancel": "Annullér",
@@ -28530,6 +28716,8 @@ Fit.Internationalization.AddLocalization(Fit.Controls.Input,
28530
28716
  },
28531
28717
  "de":
28532
28718
  {
28719
+ "MaxLengthExceeded": "Höchstens {0} Zeichen erlaubt",
28720
+
28533
28721
  "Detach": "Im Dialog öffnen",
28534
28722
  "Ok": "OK",
28535
28723
  "Cancel": "Abbrechen",
@@ -28552,6 +28740,8 @@ Fit.Internationalization.AddLocalization(Fit.Controls.Input,
28552
28740
  },
28553
28741
  "no":
28554
28742
  {
28743
+ "MaxLengthExceeded": "Maksimalt {0} tegn tillatt",
28744
+
28555
28745
  "Detach": "Åpne i dialog",
28556
28746
  "Ok": "OK",
28557
28747
  "Cancel": "Avbryt",
@@ -31704,7 +31894,7 @@ Fit.Controls.TreeView = function(ctlId)
31704
31894
  // starting from the top again. Therefore, if node passed is visible
31705
31895
  // in the hierarcy (not hidden behind collapsed nodes), then use it.
31706
31896
 
31707
- nodeIsVisibleInHierarchy = true;
31897
+ var nodeIsVisibleInHierarchy = true;
31708
31898
 
31709
31899
  var parent = node;
31710
31900
  while ((parent = parent.GetParent()) !== null)