@luomus/laji-form 15.1.41 → 15.1.42

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/styles.css CHANGED
@@ -2222,10 +2222,15 @@ body .laji-form {
2222
2222
  font-weight: bold;
2223
2223
  display: inline-block;
2224
2224
  text-align: center;
2225
+ cursor: pointer;
2225
2226
  }
2226
2227
  .laji-form .laji-form-help-glyph:before {
2227
2228
  content: "?";
2228
2229
  }
2230
+ .laji-form .laji-form-help-glyph:focus {
2231
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,1);
2232
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,1);
2233
+ }
2229
2234
 
2230
2235
  .laji-form .laji-form-map-container {
2231
2236
  z-index: 1;
@@ -2848,6 +2853,11 @@ body .laji-form {
2848
2853
  .laji-form .laji-form-error-list .btn-link:focus {
2849
2854
  outline: none;
2850
2855
  }
2856
+ .laji-form .btn-group.laji-form-checkbox-widget-tab-target:focus {
2857
+ -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,1);
2858
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,1);
2859
+ outline: none;
2860
+ }
2851
2861
  .laji-form-error-list .laji-form-error-list-expand {
2852
2862
  font-size: 12px;
2853
2863
  }
@@ -2869,6 +2879,22 @@ body .laji-form {
2869
2879
  display: table;
2870
2880
  }
2871
2881
 
2882
+ .laji-form-checkbox-selected-value-glyph, .laji-form-checkbox-unselected-value-glyph {
2883
+ pointer-events: none;
2884
+ }
2885
+ .laji-form-checkbox-selected-value-glyph:before, .laji-form-checkbox-unselected-value-glyph:before {
2886
+ content: "•";
2887
+ margin-right: 6px;
2888
+ height: 0;
2889
+ font-weight: 900;
2890
+ color: #9e9e9e;
2891
+ font-size: 18px;
2892
+ vertical-align: -2px;
2893
+ }
2894
+ .laji-form-checkbox-unselected-value-glyph:before {
2895
+ visibility: hidden;
2896
+ }
2897
+
2872
2898
  .laji-form.map-dialog .modal-content, .laji-form.map-dialog .modal-body {
2873
2899
  width: auto;
2874
2900
  height: inherit;
@@ -3869,4 +3895,11 @@ body .laji-form {
3869
3895
  height: 25px;
3870
3896
  }
3871
3897
 
3898
+ .laji-form-label-help-margin {
3899
+ padding-right: 25px;
3900
+ }
3901
+
3902
+ .laji-form-label-help-margin-clear {
3903
+ margin-left: -20px !important;
3904
+ }
3872
3905
 
@@ -1,9 +1,21 @@
1
1
  export function AddButton({ onClick }: {
2
2
  onClick: any;
3
3
  }): JSX.Element;
4
- export function Help({ help, id }: {
4
+ /**
5
+ * @param standalone If provided, the help icon will handle **accessibility** itself.
6
+ * If not provided, the parent element must take care of showing the tooltip.
7
+ *
8
+ * **accessibility** means that it handles showing the tooltip on focus & hover, and
9
+ */
10
+ export function Help({ help, id, focusable, onFocus, onBlur, className, onClick, standalone }: {
5
11
  help: any;
6
12
  id: any;
13
+ focusable?: boolean | undefined;
14
+ onFocus: any;
15
+ onBlur: any;
16
+ className: any;
17
+ onClick: any;
18
+ standalone: any;
7
19
  }): JSX.Element;
8
20
  export function Label({ label, children, id, required, registry, uiSchema }: {
9
21
  label: any;
@@ -236,8 +236,9 @@ class Affix extends React.Component {
236
236
  window.removeEventListener("resize", this.onResize);
237
237
  }
238
238
  componentDidUpdate(prevProps, prevState) {
239
- if (!this.props.onAffixChange || !prevState || !this.state)
239
+ if (!this.props.onAffixChange || !prevState || !this.state) {
240
240
  return;
241
+ }
241
242
  if (prevState.affixState !== AFFIXED && this.state.affixState === AFFIXED) {
242
243
  this.props.onAffixChange(true);
243
244
  }
@@ -355,15 +356,32 @@ class Stretch extends React.Component {
355
356
  }
356
357
  }
357
358
  exports.Stretch = Stretch;
358
- function Help({ help, id }) {
359
- const helpGlyph = React.createElement("span", { className: "laji-form-help-glyph text-muted" });
359
+ /**
360
+ * @param standalone If provided, the help icon will handle **accessibility** itself.
361
+ * If not provided, the parent element must take care of showing the tooltip.
362
+ *
363
+ * **accessibility** means that it handles showing the tooltip on focus & hover, and
364
+ */
365
+ function Help({ help, id, focusable = false, onFocus, onBlur, className, onClick, standalone }) {
360
366
  const { Tooltip } = React.useContext(ReactContext_1.default).theme;
361
- return help ? (React.createElement(OverlayTrigger, { placement: "right", overlay: React.createElement(Tooltip, { id: id },
362
- React.createElement("span", { dangerouslySetInnerHTML: { __html: help } })) }, helpGlyph)) : helpGlyph;
367
+ const [focused, setFocused] = React.useState(undefined);
368
+ const onHelpFocus = React.useCallback(() => {
369
+ setFocused(true);
370
+ }, []);
371
+ const onHelpBlur = React.useCallback(() => {
372
+ setFocused(false);
373
+ }, []);
374
+ const helpGlyph = React.createElement("span", { className: utils_1.classNames("laji-form-help-glyph", "text-muted", className), tabIndex: focusable ? 0 : -1, onFocus: standalone ? onHelpFocus : onFocus, onBlur: standalone ? onHelpBlur : onBlur, onClick: onClick });
375
+ const tooltip = React.createElement(Tooltip, { id: id },
376
+ React.createElement("span", { dangerouslySetInnerHTML: { __html: help } }));
377
+ return help ? (React.createElement(OverlayTrigger, { placement: "right", overlay: tooltip, show: standalone && focused || undefined },
378
+ React.createElement(React.Fragment, null,
379
+ helpGlyph,
380
+ standalone && React.createElement("div", { id: `${id}--help`, style: { display: "none" } }, help)))) : helpGlyph;
363
381
  }
364
382
  exports.Help = Help;
365
383
  function Label({ label, children, id, required, registry = {}, uiSchema = {} }) {
366
- const { "ui:help": help, "ui:helpHoverable": helpHoverable, "ui:helpPlacement": helpPlacement } = uiSchema;
384
+ const { "ui:help": help, "ui:helpHoverable": helpHoverable, "ui:helpPlacement": helpPlacement, labelComponent } = uiSchema;
367
385
  const showHelp = label && help;
368
386
  const { Tooltip } = React.useContext(ReactContext_1.default).theme;
369
387
  const tooltipElem = (React.createElement(Tooltip, { id: id + "-tooltip" }, help ? (React.createElement("span", null,
@@ -371,12 +389,25 @@ function Label({ label, children, id, required, registry = {}, uiSchema = {} })
371
389
  React.createElement("br", null),
372
390
  React.createElement("span", { dangerouslySetInnerHTML: { __html: help } }))) : label));
373
391
  const requiredHtml = required ? "<span class='text-danger'>*</span>" : "";
374
- const labelElem = (React.createElement("label", { htmlFor: id },
392
+ const [focused, setFocused] = React.useState(undefined);
393
+ const onHelpFocus = React.useCallback(() => {
394
+ setFocused(true);
395
+ }, []);
396
+ const onHelpBlur = React.useCallback(() => {
397
+ setFocused(false);
398
+ }, []);
399
+ const onHelpClick = React.useCallback((e) => {
400
+ e.preventDefault();
401
+ }, []);
402
+ const LabelComponent = labelComponent || "label";
403
+ const labelElem = (React.createElement(LabelComponent, { htmlFor: id, "aria-describedby": `${id}--help` },
375
404
  React.createElement("div", null,
376
405
  React.createElement("strong", { dangerouslySetInnerHTML: { __html: label + requiredHtml } }),
377
- showHelp ? React.createElement(Help, null) : null),
406
+ showHelp ? React.createElement(Help, { focusable: true, onFocus: onHelpFocus, onBlur: onHelpBlur, onClick: onHelpClick }) : null),
378
407
  children));
379
- return (label || help) ? (React.createElement(OverlayTrigger, { placement: helpPlacement || "right", overlay: tooltipElem, hoverable: helpHoverable, formContext: registry.formContext }, labelElem)) : labelElem;
408
+ return help ? React.createElement(React.Fragment, null,
409
+ React.createElement(OverlayTrigger, { placement: helpPlacement || "right", overlay: tooltipElem, hoverable: helpHoverable, formContext: registry.formContext, show: focused || undefined }, labelElem),
410
+ React.createElement("div", { id: `${id}--help`, style: { display: "none" } }, help)) : labelElem;
380
411
  }
381
412
  exports.Label = Label;
382
413
  class ErrorPanel extends React.Component {
@@ -479,11 +510,16 @@ class OverlayTrigger extends React.Component {
479
510
  if (this.overlayTimeout) {
480
511
  clearTimeout(this.overlayTimeout);
481
512
  }
482
- this.overlayTimeout = this.props.formContext.setTimeout(() => {
483
- if (!this.popoverMouseIn && !this.overlayTriggerMouseIn) {
484
- this.setState({ hoveringElem: false });
485
- }
486
- }, 200);
513
+ if (this.props.hoverable) {
514
+ this.overlayTimeout = this.props.formContext.setTimeout(() => {
515
+ if (!this.popoverMouseIn && !this.overlayTriggerMouseIn) {
516
+ this.setState({ hoveringElem: false });
517
+ }
518
+ }, 200);
519
+ }
520
+ else {
521
+ this.setState({ hoveringElem: false });
522
+ }
487
523
  };
488
524
  this.overlayMouseOver = () => {
489
525
  this.setState({ hoveringOverlay: true });
@@ -502,10 +538,10 @@ class OverlayTrigger extends React.Component {
502
538
  const _a = this.props, { children, overlay, contextId } = _a, //eslint-disable-line @typescript-eslint/no-unused-vars
503
539
  props = __rest(_a, ["children", "overlay", "contextId"]);
504
540
  const { OverlayTrigger } = this.context.theme;
505
- if (!this.props.hoverable)
506
- return (React.createElement(OverlayTrigger, Object.assign({}, props, { overlay: overlay }), children));
507
541
  let _overlay = React.cloneElement(overlay, { onMouseOver: this.overlayMouseOver, onMouseOut: this.overlayMouseOut });
508
- const show = this.state.hoveringElem || this.state.hoveringOverlay;
542
+ const show = this.props.show !== undefined
543
+ ? this.props.show
544
+ : this.state.hoveringElem || this.props.hoverable && this.state.hoveringOverlay;
509
545
  return (React.createElement("div", { onMouseOver: this.overlayTriggerMouseOver, onMouseOut: this.overlayTriggerMouseOut },
510
546
  React.createElement(OverlayTrigger, Object.assign({}, props, { delay: 1, trigger: [], placement: this.props.placement || "top", overlay: _overlay, show: show }), children)));
511
547
  }
@@ -68,14 +68,7 @@ let SingleActiveArrayField = class SingleActiveArrayField extends React.Componen
68
68
  });
69
69
  };
70
70
  this.onHeaderAffixChange = (elem, value) => {
71
- if (value) {
72
- this.setState({ scrollHeightFixed: elem.scrollHeight }, () => {
73
- this.getLocalFormContext().utils.syncScroll(!!"force");
74
- });
75
- }
76
- else {
77
- this.setState({ scrollHeightFixed: 0 });
78
- }
71
+ this.setState({ scrollHeightFixed: value ? elem.scrollHeight : 0 });
79
72
  };
80
73
  this.getLocalFormContext = () => {
81
74
  if (this.localContextKey === this.state.scrollHeightFixed) {
@@ -940,6 +933,15 @@ class AccordionHeader extends React.Component {
940
933
  };
941
934
  });
942
935
  };
936
+ this.onHelpFocus = () => {
937
+ this.setState({ focused: true });
938
+ };
939
+ this.onHelpBlur = () => {
940
+ this.setState({ focused: false });
941
+ };
942
+ this.onHelpClick = (e) => {
943
+ e.preventDefault();
944
+ };
943
945
  }
944
946
  render() {
945
947
  const { that, idx } = this.props;
@@ -947,7 +949,10 @@ class AccordionHeader extends React.Component {
947
949
  const popupData = that.state.popups[idx];
948
950
  const { uiSchema } = that.props;
949
951
  const hasHelp = uiSchema && uiSchema["ui:help"];
950
- const headerText = (React.createElement("span", null,
952
+ const spanProps = hasHelp
953
+ ? { "aria-describedby": `${that.props.idSchema.$id}_${idx}--help` }
954
+ : {};
955
+ const headerText = (React.createElement("span", Object.assign({}, spanProps),
951
956
  title,
952
957
  this.getFormatters().map((formatter, i) => {
953
958
  const { component: Formatter } = formatter;
@@ -955,15 +960,20 @@ class AccordionHeader extends React.Component {
955
960
  " ",
956
961
  React.createElement(Formatter, { that: that, idx: idx }))) || null;
957
962
  }),
958
- hasHelp ? React.createElement(components_1.Help, null) : null));
959
- const headerTextComponent = hasHelp ? React.createElement(components_1.TooltipComponent, { tooltip: uiSchema["ui:help"] }, headerText) : headerText;
963
+ hasHelp &&
964
+ React.createElement(React.Fragment, null,
965
+ React.createElement(components_1.Help, { focusable: true, onFocus: this.onHelpFocus, onBlur: this.onHelpBlur, onClick: this.onHelpClick }),
966
+ React.createElement("div", { id: `${that.props.idSchema.$id}_${idx}--help`, style: { display: "none" } }, uiSchema["ui:help"]))));
967
+ const { Tooltip } = this.context.theme;
968
+ const tooltip = React.createElement(Tooltip, null, uiSchema["ui:help"]);
969
+ const headerTextComponent = hasHelp ? React.createElement(components_1.OverlayTrigger, { placement: "right", overlay: tooltip, show: this.state && this.state.focused || undefined }, headerText) : headerText;
960
970
  const header = (React.createElement("div", { className: this.props.className, role: "tab", id: `${that.props.idSchema.$id}_${utils_1.getFormDataIndex(idx, that.props.uiSchema)}-header`, onClick: this.onHeaderClick, onMouseEnter: this.onMouseEnter, onMouseLeave: this.onMouseLeave },
961
971
  React.createElement("div", { className: this.props.wrapperClassName },
962
972
  headerTextComponent,
963
973
  this.props.children)));
964
- const { OverlayTrigger, Tooltip } = this.context.theme;
965
- return utils_1.hasData(popupData) ? (React.createElement(OverlayTrigger, { placement: "left", overlay: React.createElement(Tooltip, { id: "nav-tooltip-" + idx },
974
+ return utils_1.hasData(popupData) ? (React.createElement(components_1.OverlayTrigger, { placement: "left", overlay: React.createElement(Tooltip, { id: "nav-tooltip-" + idx },
966
975
  React.createElement(Popup, { data: popupData })) }, header)) : (header);
967
976
  }
968
977
  }
969
978
  AccordionHeader.contextType = ReactContext_1.default;
979
+ AccordionHeader.state = { focused: false };
@@ -91,7 +91,7 @@ class _FieldTemplate extends React.Component {
91
91
  inlineHelp ? React.createElement("div", { className: "pull-left" }, children) : children,
92
92
  inlineHelp
93
93
  ? (React.createElement("div", { className: "pull-left" },
94
- React.createElement(components_1.Help, { help: inlineHelp, id: `${htmlId}-inline-help` }))) : null),
94
+ React.createElement(components_1.Help, { help: inlineHelp, id: `${htmlId}-inline-help`, focusable: true, standalone: true }))) : null),
95
95
  belowHelp ?
96
96
  React.createElement("div", { className: "small text-muted", dangerouslySetInnerHTML: { __html: belowHelp } }) :
97
97
  null,
@@ -13,10 +13,20 @@ const TitleField = ({ title, id, formData, style, uiSchema = {}, registry = {} }
13
13
  return _titleFormatters[renderer](Object.assign(Object.assign({}, titleFormatter), { formData }));
14
14
  }).filter(i => i);
15
15
  const { Tooltip } = React.useContext(ReactContext_1.default).theme;
16
+ const [focused, setFocused] = React.useState(undefined);
17
+ const onHelpFocus = React.useCallback(() => {
18
+ setFocused(true);
19
+ }, []);
20
+ const onHelpBlur = React.useCallback(() => {
21
+ setFocused(false);
22
+ }, []);
23
+ const onHelpClick = React.useCallback((e) => {
24
+ e.preventDefault();
25
+ }, []);
16
26
  if (renderedFormatters.length === 0 && utils_1.isEmptyString(title)) {
17
27
  return null;
18
28
  }
19
- const helpComponent = help ? React.createElement(components_1.Help, null) : null;
29
+ const helpComponent = help ? React.createElement(components_1.Help, { focusable: true, onFocus: onHelpFocus, onBlur: onHelpBlur, onClick: onHelpClick }) : null;
20
30
  let titleTextContent = React.createElement("span", null,
21
31
  React.createElement("span", { dangerouslySetInnerHTML: { __html: title } }),
22
32
  " ",
@@ -27,7 +37,7 @@ const TitleField = ({ title, id, formData, style, uiSchema = {}, registry = {} }
27
37
  React.createElement("strong", { dangerouslySetInnerHTML: { __html: title } }),
28
38
  React.createElement("br", null),
29
39
  React.createElement("span", { dangerouslySetInnerHTML: { __html: help } }))));
30
- titleTextContent = (React.createElement(components_1.OverlayTrigger, { placement: "right", overlay: tooltipElem, hoverable: helpHoverable, formContext: registry.formContext }, titleTextContent));
40
+ titleTextContent = (React.createElement(components_1.OverlayTrigger, { placement: "right", overlay: tooltipElem, hoverable: helpHoverable, formContext: registry.formContext, show: focused || undefined }, titleTextContent));
31
41
  }
32
42
  return (React.createElement("legend", { className: utils_1.classNames(className, help && "has-help"), style: style },
33
43
  React.createElement("span", null,
@@ -1026,7 +1026,6 @@ class ReactAutosuggest extends React.Component {
1026
1026
  };
1027
1027
  this.onInputKeyDown = (e) => {
1028
1028
  let state, suggestion;
1029
- const { shortcuts } = this.props.formContext.services.keyHandler;
1030
1029
  switch (e.key) {
1031
1030
  case "ArrowDown":
1032
1031
  e.preventDefault();
@@ -1057,13 +1056,8 @@ class ReactAutosuggest extends React.Component {
1057
1056
  case "Enter":
1058
1057
  e.preventDefault();
1059
1058
  suggestion = (this.props.suggestions || [])[this.state.focusedIdx];
1060
- if (shortcuts.Enter) {
1061
- this.props.formContext.setTimeout(() => {
1062
- suggestion && this.onSuggestionSelected(suggestion);
1063
- }, 0);
1064
- }
1065
- else {
1066
- this.inputElem.blur();
1059
+ if (suggestion) {
1060
+ this.onSuggestionSelected(suggestion);
1067
1061
  }
1068
1062
  break;
1069
1063
  case "Control":
@@ -27,9 +27,9 @@ export default class CheckboxWidget extends React.Component<any, any, any> {
27
27
  getOptions: (props: any) => any;
28
28
  getToggleMode: (props: any) => boolean;
29
29
  onGroupKeyDown: any;
30
- onTrueKeyDown: (e: any) => void;
31
- onFalseKeyDown: (e: any) => void;
32
- onUndefinedKeyDown: (e: any) => void;
30
+ onTrueKeyDown: any;
31
+ onFalseKeyDown: any;
32
+ onUndefinedKeyDown: any;
33
33
  toggle: (e: any) => void;
34
34
  formatValue(value: any, options: any, props: any): any;
35
35
  }
@@ -38,38 +38,28 @@ class CheckboxWidget extends React.Component {
38
38
  return Object.assign({ allowUndefined: true, showUndefined: true, invert: false, trueLabel: Yes, falseLabel: No, unknownLabel: Unknown, required: this.props.required }, utils_1.getUiOptions(props));
39
39
  };
40
40
  this.getToggleMode = (props) => {
41
- const { allowUndefined, showUndefined, falseLabel, trueLabel } = this.getOptions(props);
41
+ const { allowUndefined, showUndefined } = this.getOptions(props);
42
42
  const displayUndefined = (allowUndefined && showUndefined);
43
- const { Yes, No } = props.registry.formContext.translations;
44
- return !displayUndefined && (trueLabel === Yes && falseLabel === No);
43
+ return !displayUndefined;
45
44
  };
46
45
  this.onGroupKeyDown = this.props.formContext.utils.keyboardClick((e) => {
47
46
  this.getToggleMode(this.props) && this.toggle(e);
48
- }, [" "]);
49
- this.onTrueKeyDown = (e) => {
50
- const { key } = e;
51
- if (key === " ") {
52
- this.onChange(true);
53
- e.preventDefault();
54
- e.stopPropagation();
55
- }
56
- };
57
- this.onFalseKeyDown = (e) => {
58
- const { key } = e;
59
- if (key === " ") {
60
- this.onChange(false);
61
- e.preventDefault();
62
- e.stopPropagation();
63
- }
64
- };
65
- this.onUndefinedKeyDown = (e) => {
66
- const { key } = e;
67
- if (key === " ") {
68
- this.onChange(undefined);
69
- e.preventDefault();
70
- e.stopPropagation();
71
- }
72
- };
47
+ });
48
+ this.onTrueKeyDown = this.props.formContext.utils.keyboardClick((e) => {
49
+ this.onChange(true);
50
+ e.preventDefault();
51
+ e.stopPropagation();
52
+ });
53
+ this.onFalseKeyDown = this.props.formContext.utils.keyboardClick((e) => {
54
+ this.onChange(false);
55
+ e.preventDefault();
56
+ e.stopPropagation();
57
+ });
58
+ this.onUndefinedKeyDown = this.props.formContext.utils.keyboardClick((e) => {
59
+ this.onChange(undefined);
60
+ e.preventDefault();
61
+ e.stopPropagation();
62
+ });
73
63
  this.toggle = (e) => {
74
64
  if (this.props.disabled || this.props.readonly) {
75
65
  return;
@@ -112,15 +102,23 @@ class CheckboxWidget extends React.Component {
112
102
  tabIndex: (toggleMode || _disabled) ? undefined : 0
113
103
  };
114
104
  const tabTargetClass = "laji-form-checkbox-widget-tab-target";
105
+ const selectedValueGlyph = React.createElement("span", { className: "laji-form-checkbox-selected-value-glyph" });
106
+ const unselectedValueGlyph = React.createElement("span", { className: "laji-form-checkbox-unselected-value-glyph" });
115
107
  const checkbox = (React.createElement(ButtonToolbar, { className: utils_1.classNames("laji-form-checkbox-buttons", toggleMode && "desktop-layout") },
116
108
  React.createElement(ToggleButtonGroup, Object.assign({ ref: this.groupRef, type: "radio", value: [_value], name: this.props.id, onChange: this.onButtonGroupChange, onKeyDown: this.onGroupKeyDown, className: utils_1.classNames(toggleMode && tabTargetClass) }, commonProps, { tabIndex: (toggleMode && !_disabled) ? 0 : undefined }),
117
- React.createElement(ToggleButton, Object.assign({ id: `${id}-true`, ref: this.trueRef, value: true, onClick: toggleMode ? this.toggle : undefined, className: utils_1.classNames(toggleMode && _value === false && "laji-form-hide-btn-label", _value === true && tabTargetClass), onKeyDown: this.onTrueKeyDown }, commonProps), trueLabel),
118
- React.createElement(ToggleButton, Object.assign({ id: `${id}-false`, ref: this.falseRef, value: false, onClick: toggleMode ? this.toggle : undefined, className: utils_1.classNames(toggleMode && _value === true && "laji-form-hide-btn-label", _value === false && tabTargetClass), onKeyDown: this.onFalseKeyDown }, commonProps), falseLabel),
109
+ React.createElement(ToggleButton, Object.assign({ id: `${id}-true`, ref: this.trueRef, value: true, onClick: toggleMode ? this.toggle : undefined, className: utils_1.classNames(_value === true && tabTargetClass), onKeyDown: this.onTrueKeyDown }, commonProps),
110
+ _value === true ? selectedValueGlyph : unselectedValueGlyph,
111
+ trueLabel),
112
+ React.createElement(ToggleButton, Object.assign({ id: `${id}-false`, ref: this.falseRef, value: false, onClick: toggleMode ? this.toggle : undefined, className: utils_1.classNames(_value === false && tabTargetClass), onKeyDown: this.onFalseKeyDown }, commonProps),
113
+ _value === false ? selectedValueGlyph : unselectedValueGlyph,
114
+ falseLabel),
119
115
  (displayUndefined ?
120
- React.createElement(ToggleButton, Object.assign({ id: `${id}-undefined`, ref: this.undefinedRef, value: "undefined", className: utils_1.classNames(value === undefined && tabTargetClass) }, commonProps, { onKeyDown: this.onUndefinedKeyDown }), unknownLabel) : null))));
116
+ React.createElement(ToggleButton, Object.assign({ id: `${id}-undefined`, ref: this.undefinedRef, value: "undefined", className: utils_1.classNames(value === undefined && tabTargetClass) }, commonProps, { onKeyDown: this.onUndefinedKeyDown }),
117
+ _value === "undefined" ? selectedValueGlyph : unselectedValueGlyph,
118
+ unknownLabel) : null))));
121
119
  const { Label } = this.props.formContext;
122
120
  return !hasLabel ? checkbox : React.createElement(React.Fragment, null,
123
- React.createElement(Label, { label: label, required: required, uiSchema: this.props.uiSchema, contextId: this.props.formContext.contextId }),
121
+ React.createElement(Label, { label: label, required: required, uiSchema: this.props.uiSchema, registry: this.props.registry, id: this.props.id }),
124
122
  checkbox);
125
123
  }
126
124
  formatValue(value, options, props) {
@@ -26,7 +26,6 @@ export default class TextareaWidget extends React.Component<any, any, any> {
26
26
  value: any;
27
27
  };
28
28
  }) => void;
29
- onKeyDown: (e: any) => void;
30
29
  }
31
30
  import * as React from "react";
32
31
  import * as PropTypes from "prop-types";
@@ -2,11 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const React = require("react");
4
4
  const PropTypes = require("prop-types");
5
- const utils_1 = require("../../utils");
6
- const components_1 = require("../components");
7
5
  const Context_1 = require("../../Context");
8
6
  const ReactContext_1 = require("../../ReactContext");
9
- const utils_2 = require("../../utils");
7
+ const utils_1 = require("../../utils");
10
8
  class TextareaWidget extends React.Component {
11
9
  constructor(props) {
12
10
  super(props);
@@ -35,7 +33,7 @@ class TextareaWidget extends React.Component {
35
33
  if (this.timeout)
36
34
  clearTimeout(this.timeout);
37
35
  this.timeout = this.props.formContext.setTimeout(() => {
38
- this.props.onChange(value === "" ? utils_2.getUiOptions(this.props).emptyValue : value);
36
+ this.props.onChange(value === "" ? utils_1.getUiOptions(this.props).emptyValue : value);
39
37
  }, 1000);
40
38
  }
41
39
  });
@@ -43,24 +41,9 @@ class TextareaWidget extends React.Component {
43
41
  this._onChange = ({ target: { value } }) => {
44
42
  this.onChange(value);
45
43
  };
46
- this.onKeyDown = (e) => {
47
- const inputAllowed = !this.props.disabled && !this.props.readonly;
48
- const isCtrlEnter = e.ctrlKey && e.key === "Enter";
49
- if (this.state.enterReserved && inputAllowed && isCtrlEnter) {
50
- this.onChange(this.state.value + "\n");
51
- e.preventDefault();
52
- e.stopPropagation();
53
- }
54
- };
55
44
  this.state = this.getStateFromProps(props);
56
45
  this.textareaRef = React.createRef();
57
46
  this._context = Context_1.default(props.formContext.contextId);
58
- Object.keys(props.formContext.services.keyHandler.shortcuts).some(keyCombo => {
59
- if (keyCombo === "Enter") {
60
- // // Direct mutation should be ok in constructor.
61
- this.state.enterReserved = true; // eslint-disable-line react/no-direct-mutation-state
62
- }
63
- });
64
47
  }
65
48
  UNSAFE_componentWillReceiveProps(props) {
66
49
  this.setState(this.getStateFromProps(props));
@@ -69,8 +52,7 @@ class TextareaWidget extends React.Component {
69
52
  const { id, options, placeholder, disabled, readonly, autofocus } = this.props;
70
53
  const { value } = this.state;
71
54
  const { required = this.props.required } = options;
72
- const textarea = React.createElement("textarea", { id: id, className: "form-control", value: typeof value === "undefined" ? "" : value, placeholder: placeholder, required: required, disabled: disabled, readOnly: readonly, autoFocus: autofocus, rows: options.rows, onFocus: this.onFocus, onBlur: this.onBlur, onChange: this._onChange, onKeyDown: this.onKeyDown });
73
- return this.state.enterReserved ? (React.createElement(components_1.TooltipComponent, { tooltip: `${utils_1.stringifyKeyCombo("ctrl+Enter")} ${this.props.formContext.translations.textareaHint}`, placement: "bottom" }, textarea)) : textarea;
55
+ return React.createElement("textarea", { id: id, className: "form-control", value: typeof value === "undefined" ? "" : value, placeholder: placeholder, required: required, disabled: disabled, readOnly: readonly, autoFocus: autofocus, rows: options.rows, onFocus: this.onFocus, onBlur: this.onBlur, onChange: this._onChange, onKeyDown: this.onKeyDown });
74
56
  }
75
57
  }
76
58
  exports.default = TextareaWidget;
package/lib/utils.js CHANGED
@@ -204,9 +204,10 @@ function isSelect(schema) {
204
204
  exports.isSelect = isSelect;
205
205
  const SWITCH_CLASS = "laji-form-checkbox-widget-tab-target";
206
206
  const IMG_ADD_CLASS = "laji-form-drop-zone";
207
- const inputTypes = ["input", "select", "textarea"];
208
207
  const tabbableSelectors = [
209
- ...inputTypes,
208
+ "input",
209
+ "select",
210
+ "textarea",
210
211
  `.${SWITCH_CLASS}`,
211
212
  `.${IMG_ADD_CLASS}`
212
213
  ].map(type => `${type}:not([type="hidden"]):not(:disabled):not([readonly]):not([type="file"]):not(.leaflet-control-layers-selector):not(.laji-map-input)`);
@@ -221,7 +222,7 @@ function getTabbableFields(elem, reverse) {
221
222
  exports.getTabbableFields = getTabbableFields;
222
223
  function isTabbableInput(elem) {
223
224
  return elem.id.match(/^_laji-form_/)
224
- || inputTypes.includes(elem.tagName.toLowerCase())
225
+ || ["input", "select", "textarea"].includes(elem.tagName.toLowerCase())
225
226
  || elem.className.includes(SWITCH_CLASS)
226
227
  || elem.className.includes(IMG_ADD_CLASS);
227
228
  }
@@ -301,7 +302,18 @@ exports.getNextInputInInputs = getNextInputInInputs;
301
302
  const getNextInput = (formContext) => (inputElem, reverseDirection = false) => {
302
303
  const formElem = react_dom_1.findDOMNode(formContext.formRef.current);
303
304
  const fields = getTabbableFields(formElem);
304
- return formContext.utils.getNextInputInInputs(inputElem, reverseDirection, fields);
305
+ const input = formContext.utils.getNextInputInInputs(inputElem, reverseDirection, fields);
306
+ if (!input) {
307
+ // Try to find a "tabbable element" from inside the nearest parent schema element.
308
+ const nearestParentField = findNearestParentSchemaElem(inputElem);
309
+ if (nearestParentField) {
310
+ const nearestTabbableElem = nearestParentField.querySelector(tabbableSelectorsQuery);
311
+ if (nearestTabbableElem) {
312
+ return formContext.utils.getNextInputInInputs(nearestTabbableElem, reverseDirection, fields);
313
+ }
314
+ }
315
+ }
316
+ return input;
305
317
  };
306
318
  exports.getNextInput = getNextInput;
307
319
  const focusNextInput = (formContext) => (reverseDirection = false) => {
@@ -313,12 +325,10 @@ const focusNextInput = (formContext) => (reverseDirection = false) => {
313
325
  if (uiSchema.autoFocus === false) {
314
326
  return false;
315
327
  }
316
- const width = pixelsToBsSize(window.outerWidth);
317
- if (width === "xs")
318
- return false;
319
328
  const field = formContext.utils.getNextInput(document.activeElement, reverseDirection);
320
329
  if (field) {
321
330
  field.focus();
331
+ scrollIntoViewIfNeeded(field, formContext.topOffset, formContext.bottomOffset);
322
332
  return true;
323
333
  }
324
334
  return false;
@@ -445,7 +455,7 @@ const _keyboardClick = ({ contextId }) => (fn, keys = [" ", "Enter"]) => {
445
455
  const { shortcuts } = Context_1.default(contextId);
446
456
  keys = keys.filter(k => !(shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts[k]));
447
457
  }
448
- if (keys.every(k => e.key !== k)) {
458
+ if (e.altKey || e.ctrlKey || keys.every(k => e.key !== k)) {
449
459
  return;
450
460
  }
451
461
  e.preventDefault();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luomus/laji-form",
3
- "version": "15.1.41",
3
+ "version": "15.1.42",
4
4
  "description": "React module capable of building dynamic forms from Laji form json schemas",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",