jsl-ui 9.28.7 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/docs/AutoSuggestComponent.md +17 -0
  2. package/docs/ButtonComponent.md +14 -0
  3. package/docs/CheckBoxComponent.md +12 -0
  4. package/docs/DateComponent.md +12 -0
  5. package/docs/DateRangeComponent.md +13 -0
  6. package/docs/DateTimeComponent.md +13 -0
  7. package/docs/DialogComponent.md +15 -0
  8. package/docs/ExpanderComponent.md +13 -0
  9. package/docs/FormComponent.md +15 -0
  10. package/docs/FormFieldComponent.md +15 -0
  11. package/docs/GridComponent.md +19 -0
  12. package/docs/InputComponent.md +15 -0
  13. package/docs/IntervalComponent.md +13 -0
  14. package/docs/LoadingComponent.md +12 -0
  15. package/docs/NavigationComponent.md +17 -0
  16. package/docs/Notification.md +14 -0
  17. package/docs/NotificationComponent.md +13 -0
  18. package/docs/NumberComponent.md +12 -0
  19. package/docs/OverlayComponent.md +12 -0
  20. package/docs/ProgressComponent.md +12 -0
  21. package/docs/README.md +24 -0
  22. package/docs/RibbonMenuComponent.md +14 -0
  23. package/docs/SelectComponent.md +15 -0
  24. package/docs/SplitterComponent.md +14 -0
  25. package/docs/StackPanelComponent.md +13 -0
  26. package/docs/TabsComponent.md +16 -0
  27. package/docs/TimeComponent.md +13 -0
  28. package/docs/TreeViewComponent.md +16 -0
  29. package/examples/showcase/checks-and-expanders.ts +163 -0
  30. package/examples/showcase/cultures.ts +39 -0
  31. package/examples/showcase/dates.ts +182 -0
  32. package/examples/showcase/dialogs.ts +184 -0
  33. package/examples/showcase/feedback-and-navigation.ts +174 -0
  34. package/examples/showcase/fields.ts +178 -0
  35. package/examples/showcase/fonts/Jost-Variable.ttf +0 -0
  36. package/examples/showcase/fonts/OFL-Jost.txt +93 -0
  37. package/examples/showcase/grids.ts +150 -0
  38. package/examples/showcase/index.html +60 -0
  39. package/examples/showcase/layout-and-navigation.ts +47 -0
  40. package/examples/showcase/nivona.css +98 -0
  41. package/examples/showcase/selects.ts +158 -0
  42. package/examples/showcase/showcase.css +231 -0
  43. package/examples/showcase/showcase.ts +214 -0
  44. package/examples/showcase/tabs.ts +161 -0
  45. package/examples/showcase/themes.css +83 -0
  46. package/examples/showcase/themes.ts +54 -0
  47. package/lib/components/IntervalComponent.d.ts +5 -0
  48. package/lib/components/IntervalComponent.js +29 -13
  49. package/lib/components/auto-suggest/AutoSuggestComponent.d.ts +11 -9
  50. package/lib/components/auto-suggest/AutoSuggestComponent.js +25 -38
  51. package/lib/components/button/ButtonComponent.d.ts +30 -1
  52. package/lib/components/button/ButtonComponent.js +85 -4
  53. package/lib/components/checkbox/CheckBoxComponent.d.ts +11 -1
  54. package/lib/components/checkbox/CheckBoxComponent.js +66 -56
  55. package/lib/components/checkbox/CheckBoxStyle.scss +72 -85
  56. package/lib/components/date/DateComponent.d.ts +10 -0
  57. package/lib/components/date/DateComponent.js +92 -44
  58. package/lib/components/date-range/DateRangeComponent.d.ts +14 -3
  59. package/lib/components/date-range/DateRangeComponent.js +96 -86
  60. package/lib/components/date-range/DateRangeStyle.scss +35 -11
  61. package/lib/components/date-time/DateTimeComponent.d.ts +8 -0
  62. package/lib/components/date-time/DateTimeComponent.js +64 -23
  63. package/lib/components/date-time/DateTimeStyle.scss +10 -7
  64. package/lib/components/dialog/DialogComponent.d.ts +21 -7
  65. package/lib/components/dialog/DialogComponent.js +179 -122
  66. package/lib/components/expander/ExpanderComponent.d.ts +15 -1
  67. package/lib/components/expander/ExpanderComponent.js +85 -64
  68. package/lib/components/expander/ExpanderStyle.scss +61 -47
  69. package/lib/components/form-field/FormFieldComponent.d.ts +45 -15
  70. package/lib/components/form-field/FormFieldComponent.js +249 -96
  71. package/lib/components/form-field/FormFieldStyle.scss +36 -87
  72. package/lib/components/form-field/create-control.d.ts +7 -0
  73. package/lib/components/form-field/create-control.js +48 -0
  74. package/lib/components/input/InputComponent.d.ts +18 -1
  75. package/lib/components/input/InputComponent.js +68 -93
  76. package/lib/components/loading/LoadingComponent.d.ts +9 -2
  77. package/lib/components/loading/LoadingComponent.js +13 -12
  78. package/lib/components/loading/LoadingStyle.css +19 -29
  79. package/lib/components/navigation/NavigationComponent.d.ts +34 -10
  80. package/lib/components/navigation/NavigationComponent.js +261 -33
  81. package/lib/components/navigation/NavigationStyle.scss +62 -226
  82. package/lib/components/notification/Notification.d.ts +37 -4
  83. package/lib/components/notification/Notification.js +160 -41
  84. package/lib/components/notification/Notification.scss +103 -19
  85. package/lib/components/number/NumberComponent.d.ts +16 -2
  86. package/lib/components/number/NumberComponent.js +54 -53
  87. package/lib/components/overlay/OverlayComponent.d.ts +13 -7
  88. package/lib/components/overlay/OverlayComponent.js +46 -114
  89. package/lib/components/overlay/overlay.css +18 -57
  90. package/lib/components/progress/ProgressComponent.d.ts +1 -0
  91. package/lib/components/progress/ProgressComponent.js +6 -6
  92. package/lib/components/progress/ProgressStyle.scss +5 -15
  93. package/lib/components/ribbon-menu/RibbonMenuComponent.js +2 -2
  94. package/lib/components/ribbon-menu/RibbonMenuStyle.scss +6 -76
  95. package/lib/components/select/SelectComponent.d.ts +33 -3
  96. package/lib/components/select/SelectComponent.js +312 -278
  97. package/lib/components/select/SelectDDAreaComponent.d.ts +13 -7
  98. package/lib/components/select/SelectDDAreaComponent.js +215 -256
  99. package/lib/components/select/SelectStyle.scss +204 -186
  100. package/lib/components/slider/nouislider.css +40 -54
  101. package/lib/components/stack-panel/SplitterComponent.d.ts +19 -3
  102. package/lib/components/stack-panel/SplitterComponent.js +94 -111
  103. package/lib/components/stack-panel/SplitterStyle.scss +9 -45
  104. package/lib/components/stack-panel/StackPanelComponent.js +3 -3
  105. package/lib/components/stack-panel/StackPanelStyle.scss +10 -52
  106. package/lib/components/tabs/TabsComponent.d.ts +17 -1
  107. package/lib/components/tabs/TabsComponent.js +247 -123
  108. package/lib/components/tabs/TabsStyle.scss +138 -75
  109. package/lib/components/time/TimeComponent.d.ts +5 -0
  110. package/lib/components/time/TimeComponent.js +61 -69
  111. package/lib/components/time/TimeComponent.scss +4 -4
  112. package/lib/components/tree-view/TreeViewComponent.d.ts +10 -4
  113. package/lib/components/tree-view/TreeViewComponent.js +65 -32
  114. package/lib/components/tree-view/TreeViewStyle.scss +12 -96
  115. package/lib/core/date.d.ts +2 -0
  116. package/lib/core/date.js +6 -0
  117. package/lib/core/jq.js +11 -8
  118. package/lib/grid/slick-default-theme.css +107 -31
  119. package/lib/grid/slick.creator.js +23 -5
  120. package/lib/grid/slick.formatters.js +4 -3
  121. package/lib/grid/slick.grid.css +38 -26
  122. package/lib/grid/slick.grid.d.ts +1 -0
  123. package/lib/grid/slick.grid.js +2 -3
  124. package/lib/grid.css +3413 -454
  125. package/lib/index.css +2500 -1031
  126. package/lib/index.css.map +1 -1
  127. package/lib/index.d.ts +1 -0
  128. package/lib/index.js +1 -1
  129. package/lib/index.js.map +1 -1
  130. package/lib/interaction/moveable.d.ts +1 -0
  131. package/lib/interaction/moveable.js +8 -0
  132. package/lib/interaction/resizable.d.ts +1 -0
  133. package/lib/interaction/resizable.js +9 -0
  134. package/lib/ui/auto.suggest.css +31 -14
  135. package/lib/ui/auto.suggest.d.ts +1 -0
  136. package/lib/ui/auto.suggest.js +88 -18
  137. package/lib/ui/base.css +2 -48
  138. package/lib/ui/button.css +134 -0
  139. package/lib/ui/date-input.css +11 -0
  140. package/lib/ui/date.picker/css/tiny-date-picker.css +124 -325
  141. package/lib/ui/date.picker/date-picker-options.js +4 -3
  142. package/lib/ui/date.picker/lib/date-manip.js +9 -0
  143. package/lib/ui/date.picker/mode/base-mode.d.ts +2 -1
  144. package/lib/ui/date.picker/mode/base-mode.js +112 -22
  145. package/lib/ui/date.picker/mode/dropdown-mode.d.ts +2 -4
  146. package/lib/ui/date.picker/mode/dropdown-mode.js +29 -42
  147. package/lib/ui/date.picker/mode/index.d.ts +2 -1
  148. package/lib/ui/date.picker/mode/modal-mode.d.ts +2 -1
  149. package/lib/ui/date.picker/mode/modal-mode.js +4 -2
  150. package/lib/ui/date.picker/mode/permanent-mode.d.ts +2 -1
  151. package/lib/ui/date.picker/views/day-picker.js +24 -15
  152. package/lib/ui/date.picker/views/month-picker.js +1 -1
  153. package/lib/ui/date.picker/views/year-picker.js +3 -2
  154. package/lib/ui/date.picker.d.ts +2 -1
  155. package/lib/ui/dialog.css +144 -108
  156. package/lib/ui/dialog.d.ts +19 -1
  157. package/lib/ui/dialog.js +178 -60
  158. package/lib/ui/input.css +35 -0
  159. package/lib/ui/number.input.js +50 -37
  160. package/lib/ui/theme.css +28 -0
  161. package/package.json +12 -8
  162. package/lib/components/drop-down/DropDownComponent.d.ts +0 -27
  163. package/lib/components/drop-down/DropDownComponent.js +0 -60
  164. package/lib/components/expander/OldExpanderStyle.scss +0 -37
  165. package/lib/ui/drop.down.d.ts +0 -52
  166. package/lib/ui/drop.down.js +0 -2112
@@ -1,3 +1,4 @@
1
+ import "../../ui/date-input.css";
1
2
  import { DatePicker } from "../../ui/date.picker";
2
3
  import { date } from "../../core/date";
3
4
  import { helper } from "../../core/helper";
@@ -5,12 +6,11 @@ import { EventHandler } from "../../interaction/eventhandler";
5
6
  export class DateComponent {
6
7
  constructor(options) {
7
8
  var _a;
8
- this.options = { ...{ enabled: true, id: helper.newDomGuid(), bindMode: "toModel" }, ...options };
9
- if (this.options.bindTo != null && this.options.property != null && this.options.value == null) {
10
- this.options.value = (((_a = this.options.converter) === null || _a === void 0 ? void 0 : _a.toUI) ?
11
- this.options.converter.toUI(this.options.bindTo[this.options.property]) :
12
- this.options.bindTo[this.options.property]);
9
+ this.options = { enabled: true, id: helper.newDomGuid(), bindMode: "toModel", ...options };
10
+ if (this.hasBinding() && this.options.value == null) {
11
+ this.options.value = this.readModel();
13
12
  }
13
+ this.modelValue = (_a = this.options.value) === null || _a === void 0 ? void 0 : _a.getTime();
14
14
  this.options.onChange = EventHandler.Create(this.options.onChange);
15
15
  }
16
16
  get onChange() {
@@ -18,72 +18,120 @@ export class DateComponent {
18
18
  }
19
19
  render() {
20
20
  var _a;
21
- let val = this.options.value;
22
- if (this.options.bindMode !== "toModel" && this.options.bindTo != null && this.options.property != null) {
23
- const current = this.getValue();
24
- const newVal = (((_a = this.options.converter) === null || _a === void 0 ? void 0 : _a.toUI) ?
25
- this.options.converter.toUI(this.options.bindTo[this.options.property]) :
26
- this.options.bindTo[this.options.property]);
27
- const newValStr = date.format(newVal);
28
- val = newVal;
29
- if (newValStr !== current) {
30
- if (this.currentDom != null) {
31
- this.currentDom.value = newValStr;
32
- }
21
+ if (this.options.bindMode !== "toModel" && this.hasBinding()) {
22
+ const value = this.readModel();
23
+ if ((value === null || value === void 0 ? void 0 : value.getTime()) !== this.modelValue) {
24
+ this.setValue(value);
25
+ this.modelValue = value === null || value === void 0 ? void 0 : value.getTime();
33
26
  }
34
27
  }
35
28
  return {
36
29
  tag: "input",
37
30
  attr: {
38
- id: this.options.id,
39
- class: this.options.class,
31
+ id: this.options.id, type: "text",
32
+ class: "jsl-date-input date-comp " + ((_a = this.options.class) !== null && _a !== void 0 ? _a : ""),
40
33
  style: this.options.style,
41
34
  disabled: !this.options.enabled ? "disabled" : undefined,
42
35
  placeholder: this.options.placeholder,
43
- value: date.format(val),
44
- change: this.raiseEvent,
36
+ "aria-label": this.options.ariaLabel,
37
+ autocomplete: "off",
38
+ value: this.format(this.options.value),
39
+ change: this.raiseEvent
45
40
  }
46
41
  };
47
42
  }
48
43
  getValue() {
49
- if (this.currentDom == null) {
50
- return "";
51
- }
52
- return this.currentDom.value;
44
+ var _a, _b;
45
+ return (_b = (_a = this.currentDom) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : this.format(this.options.value);
53
46
  }
54
47
  getValueAsDate() {
55
- if (this.currentDom == null) {
48
+ var _a;
49
+ try {
50
+ const text = this.getValue().trim();
51
+ const value = text ? ((_a = this.options.parse) !== null && _a !== void 0 ? _a : date.parse)(text) : null;
52
+ return value instanceof Date && !isNaN(value.getTime()) ? value : null;
53
+ }
54
+ catch (_b) {
56
55
  return null;
57
56
  }
58
- if (this.options.parse) {
59
- return this.options.parse(this.currentDom.value);
57
+ }
58
+ isValid() {
59
+ if (!this.getValue().trim()) {
60
+ return true;
61
+ }
62
+ const value = this.getValueAsDate();
63
+ if (!value) {
64
+ return false;
60
65
  }
61
- return date.parse(this.currentDom.value);
66
+ const min = this.parseBoundary(this.options.min);
67
+ const max = this.parseBoundary(this.options.max);
68
+ const stamp = new Date(value.getFullYear(), value.getMonth(), value.getDate()).getTime();
69
+ return !(min && stamp < min.getTime()) && !(max && stamp > max.getTime()) &&
70
+ (!this.options.inRange || this.options.inRange(value));
62
71
  }
63
- getRef() {
64
- return this.ref;
72
+ /** Updates the input and calendar without raising onChange. */
73
+ setValue(value) {
74
+ var _a;
75
+ this.options.value = value ? new Date(value.getTime()) : null;
76
+ if (this.currentDom) {
77
+ this.currentDom.value = this.format(this.options.value);
78
+ this.currentDom.setCustomValidity("");
79
+ this.currentDom.removeAttribute("aria-invalid");
80
+ }
81
+ (_a = this.ref) === null || _a === void 0 ? void 0 : _a.setState({ selectedDate: this.options.value }, true);
65
82
  }
83
+ getRef() { return this.ref; }
66
84
  onCreate(node) {
67
85
  this.currentDom = node.dom;
68
86
  this.ref = DatePicker(this.currentDom, this.options);
69
87
  }
70
- onUpdate(node) {
71
- this.currentDom = node.dom;
88
+ onUpdate(node) { this.currentDom = node.dom; }
89
+ onRemove() { var _a; (_a = this.ref) === null || _a === void 0 ? void 0 : _a.destroy(); }
90
+ format(value) {
91
+ var _a;
92
+ return value ? ((_a = this.options.format) !== null && _a !== void 0 ? _a : date.format)(value) : "";
72
93
  }
73
- onRemove() {
74
- if (this.ref != null) {
75
- this.ref.destroy();
76
- }
94
+ hasBinding() {
95
+ return this.options.bindTo != null && this.options.property != null;
96
+ }
97
+ readModel() {
98
+ var _a;
99
+ const value = this.options.bindTo[this.options.property];
100
+ return ((_a = this.options.converter) === null || _a === void 0 ? void 0 : _a.toUI) ? this.options.converter.toUI(value) : value;
77
101
  }
78
102
  raiseEvent(e, node) {
79
103
  var _a;
80
- if (this.options.enabled) {
81
- const val = date.parse(node.dom.value);
82
- if (this.options.bindTo && this.options.bindMode !== "fromModel" && this.options.property) {
83
- this.options.bindTo[this.options.property] =
84
- ((_a = this.options.converter) === null || _a === void 0 ? void 0 : _a.toData) ? this.options.converter.toData(val) : val;
85
- }
86
- this.options.onChange.notify(val);
104
+ if (!this.options.enabled) {
105
+ return;
106
+ }
107
+ this.currentDom = node.dom;
108
+ const value = this.getValueAsDate();
109
+ const invalid = !this.isValid();
110
+ this.currentDom.setCustomValidity(invalid ? "Enter an available date." : "");
111
+ if (invalid) {
112
+ this.currentDom.setAttribute("aria-invalid", "true");
113
+ return false;
114
+ }
115
+ this.currentDom.removeAttribute("aria-invalid");
116
+ this.options.value = value;
117
+ if (this.hasBinding() && this.options.bindMode !== "fromModel") {
118
+ this.options.bindTo[this.options.property] =
119
+ ((_a = this.options.converter) === null || _a === void 0 ? void 0 : _a.toData) ? this.options.converter.toData(value) : value;
120
+ this.modelValue = value === null || value === void 0 ? void 0 : value.getTime();
121
+ }
122
+ this.onChange.notify(value);
123
+ }
124
+ parseBoundary(value) {
125
+ var _a;
126
+ if (value == null) {
127
+ return null;
128
+ }
129
+ try {
130
+ const parsed = typeof value === "string" ? ((_a = this.options.parse) !== null && _a !== void 0 ? _a : date.parse)(value) : value;
131
+ return parsed ? new Date(parsed.getFullYear(), parsed.getMonth(), parsed.getDate()) : null;
132
+ }
133
+ catch (_b) {
134
+ return null;
87
135
  }
88
136
  }
89
137
  }
@@ -14,22 +14,33 @@ export interface IDateRangeOptions {
14
14
  maxValue?: Date;
15
15
  increment: "days" | "weeks" | "month" | "years";
16
16
  onChange?: EventHandler<IDateRangeValue> | ((v?: IDateRangeValue) => void);
17
+ enabled?: boolean;
18
+ ariaLabel?: string;
19
+ startLabel?: string;
20
+ endLabel?: string;
17
21
  }
18
22
  export declare class DateRangeComponent implements IJSLComponent {
19
23
  State: {
20
- currentMin: any;
21
- currentMax: any;
24
+ currentMin: number;
25
+ currentMax: number;
22
26
  };
23
- private manualChange;
24
27
  private minDate;
25
28
  private maxDate;
26
29
  private options;
27
30
  private slider;
31
+ private days;
32
+ private startId;
33
+ private endId;
28
34
  constructor(o: IDateRangeOptions);
29
35
  render(): IJSLVNode;
30
36
  onCreate(node: IJSLVNode): void;
31
37
  onRemove(): void;
32
38
  getMin(): Date;
33
39
  getMax(): Date;
40
+ private day;
41
+ private offset;
42
+ private at;
43
+ private syncFields;
44
+ private inputChange;
34
45
  private triggerChange;
35
46
  }
@@ -2,120 +2,130 @@ import "./DateRangeStyle.scss";
2
2
  import { noUiSlider } from "../slider/nouislider";
3
3
  import { DateComponent } from "../date/DateComponent";
4
4
  import { date } from "../../core/date";
5
+ import { helper } from "../../core/helper";
5
6
  import { EventHandler } from "../../interaction/eventhandler";
6
7
  export class DateRangeComponent {
7
8
  constructor(o) {
9
+ var _a, _b;
8
10
  this.State = { currentMin: null, currentMax: null };
9
- this.manualChange = false;
10
- this.options = { ...o };
11
- if (this.options.minValue == null) {
12
- this.options.minValue = this.options.min;
11
+ this.startId = helper.newDomGuid();
12
+ this.endId = helper.newDomGuid();
13
+ this.options = { enabled: true, startLabel: "Start date", endLabel: "End date", ...o };
14
+ this.options.min = this.day(o.min);
15
+ this.options.max = this.day(o.max);
16
+ this.days = date.getDaysBetween(this.options.min, this.options.max);
17
+ if (!Number.isFinite(this.days) || this.days < 0) {
18
+ throw new Error("DateRangeComponent requires valid min and max dates in ascending order.");
13
19
  }
14
- if (this.options.maxValue == null) {
15
- this.options.maxValue = this.options.max;
16
- }
17
- this.options.onChange = EventHandler.Create(this.options.onChange);
20
+ const start = this.offset((_a = o.minValue) !== null && _a !== void 0 ? _a : o.min);
21
+ const end = Math.max(start, this.offset((_b = o.maxValue) !== null && _b !== void 0 ? _b : o.max));
22
+ this.State.currentMin = start;
23
+ this.State.currentMax = end;
24
+ this.options.onChange = EventHandler.Create(o.onChange);
25
+ const common = { min: this.options.min, max: this.options.max, enabled: this.options.enabled };
18
26
  this.minDate = new DateComponent({
19
- min: this.options.min,
20
- max: this.options.max,
21
- value: this.options.minValue,
22
- style: "width:6rem;",
23
- onChange: (val) => {
24
- if (this.manualChange) {
25
- return;
26
- }
27
- this.manualChange = true;
28
- const min = this.minDate.getValueAsDate();
29
- const max = this.maxDate.getValueAsDate();
30
- this.slider.set([date.getDaysBetween(this.options.min, min), date.getDaysBetween(this.options.min, max)]);
31
- this.manualChange = false;
32
- this.triggerChange();
33
- }
27
+ ...common, id: this.startId, value: this.at(start),
28
+ onChange: value => this.inputChange(value, 0)
34
29
  });
35
30
  this.maxDate = new DateComponent({
36
- min: this.options.min,
37
- max: this.options.max,
38
- value: this.options.maxValue,
39
- style: "width:6rem;text-align:right;",
40
- onChange: (val) => {
41
- if (this.manualChange) {
42
- return;
43
- }
44
- this.manualChange = true;
45
- const min = this.minDate.getValueAsDate();
46
- const max = this.maxDate.getValueAsDate();
47
- this.slider.set([date.getDaysBetween(this.options.min, min), date.getDaysBetween(this.options.min, max)]);
48
- this.manualChange = false;
49
- this.triggerChange();
50
- }
31
+ ...common, id: this.endId, value: this.at(end),
32
+ onChange: value => this.inputChange(value, 1)
51
33
  });
52
34
  }
53
35
  render() {
54
- var _a;
36
+ var _a, _b;
37
+ this.syncFields();
38
+ const field = (id, label, control) => ({
39
+ tag: "div", attr: { class: "date-range-field" }, children: [
40
+ { tag: "label", attr: { for: id }, content: label }, control
41
+ ]
42
+ });
55
43
  return {
56
- tag: "div", attr: { style: this.options.style, class: "date-range-comp " + ((_a = this.options.class) !== null && _a !== void 0 ? _a : "") }, children: [
57
- {
58
- tag: "div", children: [
59
- this.minDate,
60
- { tag: "span", content: " - " },
61
- this.maxDate
62
- ]
63
- }, {
64
- tag: "div", children: [
44
+ tag: "div", attr: {
45
+ style: this.options.style, class: "date-range-comp " + ((_a = this.options.class) !== null && _a !== void 0 ? _a : ""),
46
+ role: "group", "aria-label": (_b = this.options.ariaLabel) !== null && _b !== void 0 ? _b : "Date range"
47
+ },
48
+ children: [
49
+ { tag: "div", attr: { class: "date-range-fields" }, children: [
50
+ field(this.startId, this.options.startLabel, this.minDate),
51
+ { tag: "span", content: "–", attr: { class: "date-range-separator", "aria-hidden": "true" } },
52
+ field(this.endId, this.options.endLabel, this.maxDate)
53
+ ] },
54
+ { tag: "div", attr: { class: "date-range-track" }, children: [
65
55
  { tag: "div", attr: { class: "slider-div" } }
66
- ]
67
- }
56
+ ] },
57
+ { tag: "div", attr: { class: "date-range-limits", "aria-hidden": "true" }, children: [
58
+ { tag: "span", content: date.format(this.options.min) },
59
+ { tag: "span", content: date.format(this.options.max) }
60
+ ] }
68
61
  ]
69
62
  };
70
63
  }
71
64
  onCreate(node) {
72
- const diffInDays = date.getDaysBetween(this.options.min, this.options.max);
73
65
  if (this.options.increment !== "days") {
74
- console.warn(" only days as increments are currently supported");
66
+ console.warn("DateRangeComponent currently supports daily increments only.");
75
67
  }
76
- const min = 0;
77
- const max = diffInDays;
78
- this.slider = noUiSlider.create(node.dom.querySelector(".slider-div"), {
79
- start: [date.getDaysBetween(this.options.min, this.options.minValue),
80
- date.getDaysBetween(this.options.min, this.options.maxValue)],
81
- connect: true,
82
- step: 1,
83
- range: {
84
- min,
85
- max
86
- },
68
+ const target = node.dom.querySelector(".slider-div");
69
+ if (!this.options.enabled || this.days === 0) {
70
+ target.setAttribute("disabled", "");
71
+ }
72
+ this.slider = noUiSlider.create(target, {
73
+ start: [this.State.currentMin, this.State.currentMax],
74
+ connect: true, step: 1,
75
+ range: { min: 0, max: Math.max(1, this.days) },
76
+ ariaFormat: { to: value => date.format(this.at(Math.round(value))), from: Number }
77
+ });
78
+ const handles = target.querySelectorAll("[role=slider]");
79
+ handles.forEach((handle, index) => {
80
+ handle.setAttribute("aria-label", index === 0 ? this.options.startLabel : this.options.endLabel);
81
+ if (!this.options.enabled || this.days === 0) {
82
+ handle.setAttribute("tabindex", "-1");
83
+ handle.setAttribute("aria-disabled", "true");
84
+ }
87
85
  });
88
- this.slider.on("update", (values) => {
89
- if (this.manualChange) {
90
- return;
86
+ let initialized = false;
87
+ this.slider.on("update", values => {
88
+ const min = Math.min(this.days, Math.round(+values[0]));
89
+ const max = Math.min(this.days, Math.round(+values[1]));
90
+ const changed = min !== this.State.currentMin || max !== this.State.currentMax;
91
+ this.State.currentMin = min;
92
+ this.State.currentMax = max;
93
+ this.syncFields();
94
+ if (initialized && changed) {
95
+ this.triggerChange();
91
96
  }
92
- this.manualChange = true;
93
- this.State.currentMin = values[0];
94
- this.State.currentMax = values[1];
95
- const minVal = date.addDays(this.options.min, parseFloat(values[0]));
96
- const maxVal = date.addDays(this.options.min, parseFloat(values[1]));
97
- this.minDate.getRef().setState({ selectedDate: minVal });
98
- this.maxDate.getRef().setState({ selectedDate: maxVal });
99
- this.manualChange = false;
100
- this.triggerChange();
101
97
  });
98
+ initialized = true;
102
99
  }
103
- onRemove() {
104
- if (this.slider != null) {
105
- this.slider.destroy();
106
- this.slider = null;
107
- }
100
+ onRemove() { var _a; (_a = this.slider) === null || _a === void 0 ? void 0 : _a.destroy(); this.slider = null; }
101
+ getMin() { return this.at(this.State.currentMin); }
102
+ getMax() { return this.at(this.State.currentMax); }
103
+ day(value) {
104
+ return value instanceof Date ? new Date(value.getFullYear(), value.getMonth(), value.getDate()) : new Date(NaN);
108
105
  }
109
- getMin() {
110
- return this.minDate.getValueAsDate();
106
+ offset(value) {
107
+ const offset = date.getDaysBetween(this.options.min, this.day(value));
108
+ return Number.isFinite(offset) ? Math.max(0, Math.min(this.days, offset)) : 0;
111
109
  }
112
- getMax() {
113
- return this.maxDate.getValueAsDate();
110
+ at(offset) { return date.addDays(this.options.min, offset); }
111
+ syncFields() {
112
+ this.minDate.setValue(this.getMin());
113
+ this.maxDate.setValue(this.getMax());
114
+ }
115
+ inputChange(value, handle) {
116
+ if (!value || !this.slider) {
117
+ this.syncFields();
118
+ return;
119
+ }
120
+ const offset = handle === 0 ? Math.min(this.offset(value), this.State.currentMax) :
121
+ Math.max(this.offset(value), this.State.currentMin);
122
+ this.slider.set(handle === 0 ? [offset, this.State.currentMax] : [this.State.currentMin, offset]);
123
+ // Also restore the normalized value when a crossed handle could not move.
124
+ this.syncFields();
114
125
  }
115
126
  triggerChange() {
116
127
  this.options.onChange.notify({
117
- minValue: this.getMin(),
118
- maxValue: this.getMin()
128
+ minValue: this.getMin(), maxValue: this.getMax()
119
129
  });
120
130
  }
121
131
  }
@@ -1,12 +1,36 @@
1
- .date-range-comp {
2
- display: inline-block;
3
-
4
- .slider-div {
5
- margin: 0.5rem 0.9rem;
6
- }
7
-
8
- // .noUi-handle:before, .noUi-handle:after {
9
- // width: 1rem;
10
- // height: 1rem;
11
- // }
1
+ .date-range-comp {
2
+ display: inline-block;
3
+ width: 25rem;
4
+ max-width: 100%;
5
+ color: var(--jsl-color-text);
6
+ }
7
+ .date-range-fields {
8
+ display: grid;
9
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
10
+ align-items: end;
11
+ gap: .625rem;
12
+ }
13
+ .date-range-field { min-width: 0; }
14
+ .date-range-field label {
15
+ display: block;
16
+ margin-bottom: .375rem;
17
+ color: var(--jsl-color-text-secondary);
18
+ font: inherit;
19
+ font-size: .75rem;
20
+ font-weight: 500;
21
+ }
22
+ .date-range-field input { width: 100%; }
23
+ .date-range-separator { line-height: 2.5rem; color: var(--jsl-color-text-muted); }
24
+ .date-range-track { padding: 1.5rem .625rem .875rem; }
25
+ .date-range-limits {
26
+ display: flex;
27
+ justify-content: space-between;
28
+ gap: .5rem;
29
+ color: var(--jsl-color-text-muted);
30
+ font-size: .6875rem;
31
+ font-variant-numeric: tabular-nums;
32
+ }
33
+ @media (max-width: 380px) {
34
+ .date-range-fields { gap: .375rem; }
35
+ .date-range-field input { font-size: .75rem; padding-left: .5rem; padding-right: 1.75rem; background-position: right .3rem center; background-size: 15px; }
12
36
  }
@@ -3,13 +3,21 @@ import { IJSLComponent, IJSLVNode } from "jsl-render";
3
3
  import { IDateComponentOptions } from "../date/DateComponent";
4
4
  import { ITimeComponentOptions } from "../time/TimeComponent";
5
5
  export interface IDateTimeComponentOptions<T> extends IDateComponentOptions<T>, ITimeComponentOptions<T> {
6
+ dateAriaLabel?: string;
7
+ timeAriaLabel?: string;
6
8
  }
7
9
  export declare class DateTimeComponent<T = any> implements IJSLComponent {
8
10
  private options;
9
11
  private dateComp;
10
12
  private timeComp;
13
+ private modelValue;
11
14
  constructor(options: IDateTimeComponentOptions<T>);
12
15
  render(): IJSLVNode;
13
16
  getValue(): string;
14
17
  getValueAsDate(): Date;
18
+ /** Updates both fields without raising onChange. */
19
+ setValue(value: Date): void;
20
+ private hasBinding;
21
+ private readModel;
22
+ private raiseChange;
15
23
  }
@@ -1,39 +1,80 @@
1
1
  import "./DateTimeStyle.scss";
2
2
  import { EventHandler } from "../../interaction/eventhandler";
3
+ import { helper } from "../../core/helper";
3
4
  import { DateComponent } from "../date/DateComponent";
4
5
  import { TimeComponent } from "../time/TimeComponent";
5
6
  export class DateTimeComponent {
6
7
  constructor(options) {
7
- if (options.bindMode && options.bindMode !== "toModel") {
8
- throw new Error("Currently DateTimeComponent only supports toModel as bindMode");
9
- }
10
- this.options = { ...options };
8
+ var _a, _b, _c, _d;
9
+ this.options = { bindMode: "toModel", ...options };
11
10
  this.options.onChange = EventHandler.Create(this.options.onChange);
12
- const optionsCopy = { ...options };
13
- optionsCopy.bindTo = null;
14
- optionsCopy.property = null;
15
- optionsCopy.converter = null;
16
- optionsCopy.onChange = () => {
17
- var _a;
18
- const val = this.getValueAsDate();
19
- if (this.options.bindTo && options.property) {
20
- this.options.bindTo[options.property] =
21
- ((_a = this.options.converter) === null || _a === void 0 ? void 0 : _a.toData) ? this.options.converter.toData(val) : val;
22
- }
23
- this.options.onChange.notify(val);
11
+ const value = (_a = options.value) !== null && _a !== void 0 ? _a : (this.hasBinding() ? this.readModel() : null);
12
+ this.modelValue = value === null || value === void 0 ? void 0 : value.getTime();
13
+ const id = (_b = options.id) !== null && _b !== void 0 ? _b : helper.newDomGuid();
14
+ const childOptions = {
15
+ ...options, value, bindTo: null, property: null, converter: null,
16
+ bindMode: "toModel", class: undefined, style: undefined,
17
+ onChange: () => this.raiseChange()
24
18
  };
25
- this.dateComp = new DateComponent(optionsCopy);
26
- this.timeComp = new TimeComponent(optionsCopy);
19
+ this.dateComp = new DateComponent({
20
+ ...childOptions, id, ariaLabel: (_c = options.dateAriaLabel) !== null && _c !== void 0 ? _c : "Date"
21
+ });
22
+ this.timeComp = new TimeComponent({
23
+ ...childOptions, id: id + "-time", placeholder: options.inputMode === "HourMinuteSecond" ? "HH:MM:SS" : "HH:MM",
24
+ ariaLabel: (_d = options.timeAriaLabel) !== null && _d !== void 0 ? _d : "Time"
25
+ });
27
26
  }
28
27
  render() {
29
- return { tag: "div", attr: { class: "date-time-comp" }, children: [
30
- this.dateComp, this.timeComp
31
- ] };
28
+ var _a, _b;
29
+ if (this.options.bindMode !== "toModel" && this.hasBinding()) {
30
+ const value = this.readModel();
31
+ if ((value === null || value === void 0 ? void 0 : value.getTime()) !== this.modelValue) {
32
+ this.setValue(value);
33
+ this.modelValue = value === null || value === void 0 ? void 0 : value.getTime();
34
+ }
35
+ }
36
+ return {
37
+ tag: "div", attr: {
38
+ class: "date-time-comp " + ((_a = this.options.class) !== null && _a !== void 0 ? _a : ""),
39
+ style: this.options.style, role: "group",
40
+ "aria-label": (_b = this.options.ariaLabel) !== null && _b !== void 0 ? _b : "Date and time"
41
+ },
42
+ children: [this.dateComp, this.timeComp]
43
+ };
32
44
  }
33
45
  getValue() {
34
- return this.dateComp.getValue() + " " + this.timeComp.getValue();
46
+ return (this.dateComp.getValue() + " " + this.timeComp.getValue()).trim();
35
47
  }
36
48
  getValueAsDate() {
37
- return this.timeComp.getValueAsDate(this.dateComp.getValueAsDate());
49
+ const date = this.dateComp.getValueAsDate();
50
+ return date && this.dateComp.isValid() ? this.timeComp.getValueAsDate(date) : null;
51
+ }
52
+ /** Updates both fields without raising onChange. */
53
+ setValue(value) {
54
+ this.dateComp.setValue(value);
55
+ this.timeComp.setValue(value);
56
+ }
57
+ hasBinding() {
58
+ return this.options.bindTo != null && this.options.property != null;
59
+ }
60
+ readModel() {
61
+ var _a;
62
+ const value = this.options.bindTo[this.options.property];
63
+ return ((_a = this.options.converter) === null || _a === void 0 ? void 0 : _a.toUI) ? this.options.converter.toUI(value) : value;
64
+ }
65
+ raiseChange() {
66
+ var _a;
67
+ const value = this.getValueAsDate();
68
+ // Do not publish an invalid draft in either field.
69
+ if (!this.dateComp.isValid() ||
70
+ (this.timeComp.getValue() && !this.timeComp.getValueAsDate(null))) {
71
+ return;
72
+ }
73
+ if (this.hasBinding() && this.options.bindMode !== "fromModel") {
74
+ this.options.bindTo[this.options.property] =
75
+ ((_a = this.options.converter) === null || _a === void 0 ? void 0 : _a.toData) ? this.options.converter.toData(value) : value;
76
+ this.modelValue = value === null || value === void 0 ? void 0 : value.getTime();
77
+ }
78
+ this.options.onChange.notify(value);
38
79
  }
39
80
  }
@@ -1,7 +1,10 @@
1
- .date-time-comp {
2
- display: inline-block;
3
- }
4
-
5
- .date-time-comp input:first-child {
6
- width:6em;
7
- }
1
+ .date-time-comp {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ flex-wrap: wrap;
5
+ gap: .5rem;
6
+ max-width: 100%;
7
+ }
8
+ .date-time-comp .date-comp { flex: 1 1 10rem; }
9
+ .date-time-comp .time-comp { flex: 0 0 6rem; }
10
+ .date-time-comp .time-comp-seconds { flex-basis: 7.5rem; }