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
@@ -0,0 +1,178 @@
1
+ import { IJSLComponent, IJSLVNode } from "jsl-render";
2
+ import { InputComponent } from "../../components/input/InputComponent";
3
+ import { NumberComponent } from "../../components/number/NumberComponent";
4
+ import { DateComponent } from "../../components/date/DateComponent";
5
+ import { SelectComponent } from "../../components/select/SelectComponent";
6
+ import { FormFieldComponent } from "../../components/form-field/FormFieldComponent";
7
+ import { FormComponent } from "../../components/form-field/FormComponent";
8
+ import { ButtonComponent } from "../../components/button/ButtonComponent";
9
+ import { date } from "../../core/date";
10
+ import { numeric } from "../../core/numeric";
11
+
12
+ type Content = IJSLVNode | IJSLComponent;
13
+ const text = (tag: string, content: string, className?: string): IJSLVNode => ({ tag, content, attr: { class: className } });
14
+ const group = (children: Content[], className?: string): IJSLVNode => ({ tag: "div", attr: { class: className }, children });
15
+ const status = (content: string): IJSLVNode => ({ tag: "p", content, attr: { class: "sample-status", role: "status" } });
16
+ const action = (content: string, click: () => void) => new ButtonComponent({ content, click, type: "button", outlined: true, size: "small" });
17
+ const sampleField = (id: string, label: string, control: Content) => group([
18
+ { tag: "label", content: label, attr: { for: id } }, control
19
+ ], "field-sample");
20
+ function card(id: string, number: string, title: string, description: string, children: Content[]): IJSLVNode {
21
+ return { tag: "article", attr: { id, class: "sample-card" }, children: [
22
+ group([text("span", number, "sample-number"), text("h3", title)], "card-heading"),
23
+ text("p", description, "card-description"), ...children
24
+ ] };
25
+ }
26
+ function section(id: string, number: string, title: string, description: string, preview: Content, cards: IJSLVNode[], usage: Content[]): IJSLVNode {
27
+ return { tag: "section", attr: { id, class: "component-section", "aria-labelledby": id + "-title" }, children: [
28
+ group([
29
+ group([text("p", "THE ESSENTIALS / " + number, "eyebrow"),
30
+ { tag: "h2", content: title, attr: { id: id + "-title", class: "section-title" } },
31
+ text("p", description, "hero-description")], "hero-copy"),
32
+ group([text("span", "INTERACTIVE PREVIEW", "preview-caption"), preview], "hero-preview fields-preview")
33
+ ], "hero"),
34
+ group(cards, "sample-grid fields-sample-grid"),
35
+ { tag: "details", attr: { class: "usage" }, children: [text("summary", title + " usage & compatibility"), ...usage] }
36
+ ] };
37
+ }
38
+ const inputStatus = status("Start typing to see the input event.");
39
+ const textModel = { notes: "A few notes for the team." };
40
+ const textStatus = status("26 characters in the model.");
41
+ export const inputShowcase = section("inputs", "11", "Inputs",
42
+ "A consistent place to type. Simple defaults for text, passwords, and longer notes.",
43
+ new InputComponent({ id: "input-preview", placeholder: "What are you working on?", ariaLabel: "Preview input" }), [
44
+ card("input-basic", "01", "Text as you type", "Input and change events keep their familiar string values. Add an external label or use a FormField.", [
45
+ sampleField("input-name", "Project name", new InputComponent({
46
+ id: "input-name", placeholder: "Give your project a name", maxLength: 40,
47
+ onInput: value => { inputStatus.content = value ? "Typing: " + value : "The field is empty."; },
48
+ onChange: value => { inputStatus.content = "Committed: " + (value || "empty"); }
49
+ })), inputStatus
50
+ ]),
51
+ card("input-textarea", "02", "Room for a longer thought", "The same component supports a textarea, including binding on every input event.", [
52
+ sampleField("input-notes", "Notes", new InputComponent({
53
+ id: "input-notes", type: "textarea", rows: 4,
54
+ bindTo: textModel, property: "notes", bindMode: "both", bindOnInput: true,
55
+ onInput: () => { textStatus.content = textModel.notes.length + " characters in the model."; }
56
+ })), textStatus
57
+ ]),
58
+ card("input-states", "03", "Useful native input types", "Passwords, email, read-only values, and disabled fields share the same base appearance.", [
59
+ sampleField("input-password", "Password", new InputComponent({ id: "input-password", type: "password", value: "A little privacy" })),
60
+ sampleField("input-readonly", "Read-only identifier", new InputComponent({ id: "input-readonly", value: "PROJ-2026-010", readOnly: true })),
61
+ sampleField("input-disabled", "Unavailable value", new InputComponent({ id: "input-disabled", value: "Managed by your team", enabled: false }))
62
+ ]),
63
+ card("input-unstyled", "04", "A building block when needed", "Opt out of the library surface for a grid cell or a custom editor. Behavior and binding remain available.", [
64
+ group([
65
+ text("span", "PROJECT /", "inline-note"),
66
+ new InputComponent({ id: "input-plain", value: "An inline title", ariaLabel: "Inline title", unstyled: true, class: "showcase-inline-input" })
67
+ ], "unstyled-input-sample"),
68
+ text("pre", '{ unstyled: true, class: "my-editor" }', "sample-code")
69
+ ])
70
+ ], [
71
+ text("pre", 'new InputComponent({\n value: "A project", placeholder: "Project name",\n bindTo: model, property: "name", bindMode: "both",\n onChange: value => console.log(value)\n});', "sample-code"),
72
+ text("p", "Existing options, events, binding, converters, getValue(), and setValue() remain supported. The input alias for type is retained. setValue() stays silent. unstyled skips the base CSS class; ariaLabel, readOnly, rows, autocomplete, and focus() are optional additions."),
73
+ text("p", "bindOnInput now works even without an onInput subscriber. Two-way bindings preserve a draft until the model actually changes, including the caret in a textarea.")
74
+ ]);
75
+
76
+ const numberStatus = status("Commit a value with Enter or by leaving the field.");
77
+ const moneyModel = { cents: 12500 };
78
+ const moneyStatus = status("Model value: 12500 cents");
79
+ export const numberShowcase = section("numbers", "12", "Numbers",
80
+ "The same surface, with numeric behavior. Formats and converters stay close to the value.",
81
+ new NumberComponent({ id: "number-preview", value: 1250.5, format: "#,0.00", ariaLabel: "Preview amount" }), [
82
+ card("number-basic", "01", "A number with a format", "Edit a decimal, then commit it. Formatting happens on blur or Enter.", [
83
+ sampleField("number-amount", "Amount", new NumberComponent({
84
+ id: "number-amount", value: 1250.5, format: "#,0.00",
85
+ onChange: value => { numberStatus.content = value == null ? "Amount cleared." : "Numeric value: " + value; }
86
+ })), numberStatus,
87
+ sampleField("number-zero", "Whole number", new NumberComponent({ id: "number-zero", value: 0, format: "0" }))
88
+ ]),
89
+ card("number-binding", "02", "One value, two representations", "A converter presents cents as a decimal amount and converts committed edits back to cents.", [
90
+ sampleField("number-cents", "Budget", new NumberComponent({
91
+ id: "number-cents", bindTo: moneyModel, property: "cents", bindMode: "both", format: "0.00",
92
+ converter: { toUI: value => value / 100, toData: value => value == null ? null : Math.round(value * 100) },
93
+ onChange: () => { moneyStatus.content = "Model value: " + moneyModel.cents + " cents"; }
94
+ })), moneyStatus,
95
+ action("Set budget to 250.00", () => { moneyModel.cents = 25000; moneyStatus.content = "Model value: 25000 cents"; })
96
+ ]),
97
+ card("number-options", "03", "Choose when to format", "Formatting and Enter commits can be disabled independently.", [
98
+ sampleField("number-live", "Format preserved while editing", new NumberComponent({
99
+ id: "number-live", value: 12.5, format: "0.00", formatOnBlur: false, commitOnEnter: false
100
+ })),
101
+ text("pre", "{ formatOnBlur: false, commitOnEnter: false }", "sample-code")
102
+ ]),
103
+ card("number-states", "04", "Consistent states", "Disabled and read-only values match the other controls. Unstyled mode remains available for embedded editors.", [
104
+ sampleField("number-disabled", "Unavailable amount", new NumberComponent({ id: "number-disabled", value: 99, format: "0.00", enabled: false })),
105
+ sampleField("number-readonly", "Calculated total", new NumberComponent({ id: "number-readonly", value: 149.95, format: "0.00", readOnly: true })),
106
+ text("pre", '{ unstyled: true }', "sample-code")
107
+ ])
108
+ ], [
109
+ text("pre", 'new NumberComponent({\n value: 1250.5, format: "#,0.00",\n onChange: value => console.log(value)\n});', "sample-code"),
110
+ text("p", "getValue() returns the displayed string; getValueAsNumber() returns its parsed number. Formatting follows the existing culture settings and numeric helpers. Empty values remain undefined. setValue() updates writable bindings silently."),
111
+ text("p", "commitOnEnter is now honored. Enter followed by blur does not publish the same commit twice. Malformed numeric drafts remain visible for correction and do not write NaN to the model.")
112
+ ]);
113
+
114
+ const projectOptions = [{ value: "design", text: "Design system" }, { value: "website", text: "Website refresh" }, { value: "research", text: "Customer research" }];
115
+ const cities = ["Vienna", "Berlin", "Copenhagen", "London", "New York", "Singapore", "Sydney", "Tokyo"];
116
+ const validationStatus = status("Validate the form to see messages below its controls.");
117
+ const emailField = new FormFieldComponent({
118
+ label: "Email address", required: true, description: "Use your work email.",
119
+ control: new InputComponent({ id: "field-email", type: "email", placeholder: "you@example.com" })
120
+ });
121
+ const projectField = new FormFieldComponent({
122
+ label: "Project", required: true,
123
+ control: new SelectComponent({ id: "field-project", data: projectOptions, placeholder: "Choose a project" })
124
+ });
125
+ const validationForm = new FormComponent([emailField, projectField, action("Validate form", () => {
126
+ validationStatus.content = validationForm.validate() ? "All fields are valid." : "Please correct the marked fields.";
127
+ }), validationStatus]);
128
+
129
+ export const formFieldShowcase = section("form-fields", "13", "Form fields",
130
+ "A label and a little guidance. Wrap the control that fits the job.",
131
+ new FormFieldComponent({
132
+ label: "Project name", description: "Clear names help everyone find their work.",
133
+ control: new InputComponent({ id: "field-preview", value: "Component library" })
134
+ }), [
135
+ card("field-composition", "01", "Controls that belong together", "Text, numbers, and selects keep their own behavior while sharing a consistent field layout.", [
136
+ group([
137
+ new FormFieldComponent({ label: "Project name", control: new InputComponent({ id: "field-name", value: "A new project" }) }),
138
+ new FormFieldComponent({ label: "Budget", control: new NumberComponent({ id: "field-budget", value: 1200, format: "#,0.00" }) }),
139
+ new FormFieldComponent({ label: "Team", control: new SelectComponent({ id: "field-team", data: projectOptions, value: "design" }) })
140
+ ], "composed-fields"),
141
+ text("pre", 'new FormFieldComponent({\n label: "Team", control: new SelectComponent({ data })\n});', "sample-code")
142
+ ]),
143
+ card("field-legacy", "02", "Existing field types still work", "The original shorthand creates the same input components internally. Existing getValue(), setValue(), and validation calls continue to work.", [
144
+ new FormFieldComponent({ id: "field-legacy-text", label: "User name", type: "text", value: "Alex" }),
145
+ new FormFieldComponent({ id: "field-legacy-password", label: "Password", type: "password", value: "A private value" }),
146
+ text("pre", 'new FormFieldComponent({ label: "User name", type: "text" });', "sample-code")
147
+ ]),
148
+ card("field-specialized", "03", "Dates and formatted numbers", "Specialized controls handle their values and popups. The field adds the label and supporting text.", [
149
+ new FormFieldComponent({
150
+ id: "field-date", label: "Start date", type: "date", value: "15/10/2026",
151
+ dateOptions: { format: value => date.format(value, "dd/mm/yy"), parse: value => date.parse(value, "dd/mm/yy") }
152
+ }),
153
+ new FormFieldComponent({ id: "field-number", label: "Estimated cost", type: "number", value: numeric.format(1250.5, "0.00"), numberOptions: { format: "#,0.00" } }),
154
+ new FormFieldComponent({ label: "Milestone", control: new DateComponent({ id: "field-wrapped-date", value: new Date(2026, 9, 21) }) })
155
+ ]),
156
+ card("field-autosuggest", "04", "Suggestions while you type", "AutoSuggestComponent now uses the shared input styling and owns its suggestion lifecycle.", [
157
+ new FormFieldComponent({
158
+ id: "field-city", label: "Office city", type: "autosuggest", placeholder: "Start typing a city",
159
+ description: "Try Vienna, Berlin, or Tokyo.",
160
+ autoSuggestOptions: { minChars: 1, delay: 0, source: (term, suggest) => suggest(cities.filter(city => city.toLowerCase().includes(term.toLowerCase()))) }
161
+ }),
162
+ text("p", "The original autoSuggestOptions source and callbacks remain available.", "inline-note")
163
+ ]),
164
+ card("field-validation", "05", "Guidance near the control", "Required and custom validation messages appear separately from the label and are associated with the control.", [
165
+ validationForm
166
+ ]),
167
+ card("field-layout", "06", "An optional floating label", "Keep labels above controls by default, or place them on the outline. Disabled fields disable their wrapped native controls.", [
168
+ new FormFieldComponent({ id: "field-floating", label: "Workspace", labelPosition: "floating", value: "Design workspace", description: "The label stays visible when the field is empty." }),
169
+ new FormFieldComponent({ label: "Managed project", disabled: true, control: new SelectComponent({ id: "field-disabled-select", data: projectOptions, value: "design" }) }),
170
+ new FormFieldComponent({ id: "field-disabled-input", label: "Managed identifier", disabled: true, value: "PROJ-2026-010" })
171
+ ])
172
+ ], [
173
+ text("pre", 'const input = new InputComponent({ bindTo: model, property: "name", bindMode: "both" });\nconst field = new FormFieldComponent({\n label: "Project name", control: input,\n description: "Choose a name your team will recognize.",\n required: true\n});', "sample-code"),
174
+ text("p", "FormField owns labels, layout, descriptions, required indicators, and validation feedback. The child owns editing, formatting, binding, and popup cleanup. Supply control to compose an existing component; legacy type, value, dateOptions, numberOptions, and autoSuggestOptions use a compatibility factory."),
175
+ text("p", "getValue() and setValue() delegate to the wrapped control, so supplied controls use their own setter types (for example, a number or Date). Legacy number and date fields continue to accept string values. getControl() returns the child component. A generic return type can describe custom controls, for example FormFieldComponent<string[]> for a multi-select."),
176
+ text("p", "A native label targets the wrapped control's existing ID. id or controlId can specify the target. Helper and validation messages extend existing aria-describedby associations. disabled uses a native fieldset so controls such as SelectComponent are disabled together."),
177
+ text("p", "The default labelPosition is above. The optional floating presentation places an always-visible label on the outline, preserving space for a placeholder. Custom controls keep their own surfaces.")
178
+ ]);
@@ -0,0 +1,93 @@
1
+ Copyright 2020 The Jost Project Authors (https://github.com/indestructible-type)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,150 @@
1
+ import { IJSLVNode, JSLRender } from "jsl-render";
2
+ import { GridComponent } from "../../components/grid/GridComponent";
3
+ import { ISlickColumn } from "../../grid/slick.core";
4
+ import { TextEditor, NumericEditor, BooleanEditor } from "../../grid/slick.editors.core";
5
+ import { DateEditor } from "../../grid/slick.editors.date";
6
+ import { DateFormatter, NumericFormatter, CheckboxFormatter } from "../../grid/slick.formatters";
7
+ import { ButtonComponent } from "../../components/button/ButtonComponent";
8
+ import { numeric } from "../../core/numeric";
9
+
10
+ interface Project { id: number; name: string; team: string; budget: number; date: Date; approved: boolean; }
11
+ function data(count: number): Project[] {
12
+ const names = ["Design system", "Website refresh", "Customer research", "Mobile workspace", "Reporting tools"];
13
+ return Array.from({ length: count }, (_, index) => ({
14
+ id: index + 1, name: names[index % names.length] + " " + (index + 1),
15
+ team: ["Design", "Engineering", "Operations"][index % 3], budget: 1500 + index * 125,
16
+ date: new Date(2026, 9, 1 + index % 28), approved: index % 3 !== 0
17
+ }));
18
+ }
19
+ function columns(editable = false): ISlickColumn[] {
20
+ return [
21
+ { id: "id", name: "ID", width: 65, sortable: true },
22
+ { id: "name", name: "Project", width: 250, sortable: true, editor: editable ? TextEditor : undefined },
23
+ { id: "team", name: "Team", width: 145, sortable: true, editor: editable ? TextEditor : undefined },
24
+ { id: "budget", name: "Budget", width: 125, sortable: true, headerCssClass: "right", formatter: NumericFormatter, format: "#,0", editor: editable ? NumericEditor : undefined },
25
+ { id: "date", name: "Start date", width: 135, sortable: true, formatter: DateFormatter, editor: editable ? DateEditor : undefined },
26
+ { id: "approved", name: "Approved", width: 100, headerCssClass: "center", formatter: CheckboxFormatter, editor: editable ? BooleanEditor : undefined }
27
+ ];
28
+ }
29
+ const text = (tag: string, content: string): IJSLVNode => ({ tag, content });
30
+ const action = (content: string, click: () => void) => new ButtonComponent({ content, click, type: "button", outlined: true, size: "small" });
31
+ function card<T>(id: string, title: string, description: string, grid: GridComponent<T>, extras: any[] = []): IJSLVNode {
32
+ return { tag: "article", attr: { id, class: "sample-card grid-sample-card" }, children: [
33
+ text("h3", title), { tag: "p", content: description, attr: { class: "card-description" } },
34
+ ...extras, { tag: "div", attr: { class: "grid-sample-host" }, children: [grid] }
35
+ ] };
36
+ }
37
+ const basic = new GridComponent(data(30), columns(), {
38
+ primaryKey: "id", multiColumnSort: true, numberedMultiColumnSort: true
39
+ }, "300px");
40
+ const edits: IJSLVNode = { tag: "p", content: "Edit a project, budget, date, or approval value.", attr: { role: "status", class: "sample-status" } };
41
+ const editable = new GridComponent(data(25), columns(true), { primaryKey: "id", editable: true, autoEdit: false }, "300px");
42
+ editable.initEvents(grid => grid.onAfterEdit.subscribe((_event, args) => {
43
+ edits.content = "Updated " + args.column.name + " for project " + args.item.id + ".";
44
+ // Grid events run independently of the showcase renderer.
45
+ JSLRender.lastCreatedRenderer?.refresh();
46
+ }));
47
+ const grouped = new GridComponent(data(45), columns(), { primaryKey: "id" }, "320px");
48
+ grouped.initEvents(grid => grid.dataView.setGrouping({
49
+ getter: "team", formatter: group => group.value + " · " + group.count + " projects", aggregators: [], displayTotalsRow: false
50
+ }));
51
+ const frozenData = data(120).map((project, index) => ({
52
+ ...project,
53
+ owner: ["Alex Morgan", "Sam Weber", "Jordan Lee", "Robin Taylor"][index % 4],
54
+ status: ["Planning", "In progress", "In review", "Complete"][index % 4],
55
+ due: new Date(2026, 11, 1 + index % 28),
56
+ plannedHours: 80 + index * 4,
57
+ actualHours: 25 + index * 3,
58
+ location: ["Vienna", "London", "Berlin", "New York"][index % 4]
59
+ }));
60
+ const frozenColumns: ISlickColumn[] = [
61
+ ...columns().map(column => ({ ...column, width: column.id === "name" ? 300 : 180 })),
62
+ { id: "owner", name: "Project owner", width: 200, sortable: true },
63
+ { id: "status", name: "Status", width: 180, sortable: true },
64
+ { id: "due", name: "Due date", width: 180, sortable: true, formatter: DateFormatter },
65
+ { id: "plannedHours", name: "Planned hours", width: 180, sortable: true, headerCssClass: "right", formatter: NumericFormatter, format: "#,0" },
66
+ { id: "actualHours", name: "Actual hours", width: 180, sortable: true, headerCssClass: "right", formatter: NumericFormatter, format: "#,0" },
67
+ { id: "location", name: "Office", width: 180, sortable: true }
68
+ ];
69
+ const frozen = new GridComponent(frozenData, frozenColumns, {
70
+ primaryKey: "id", frozenColumn: 1
71
+ }, "320px");
72
+ const large = new GridComponent(data(10000), columns(), { primaryKey: "id", rowHeight: 28 }, "320px");
73
+
74
+ const costingData = data(12).map((project, index) => ({
75
+ ...project, hours: 16 + index * 4, rate: 85 + index % 3 * 15, cost: 0
76
+ }));
77
+ const costingColumns: ISlickColumn[] = [
78
+ { id: "id", name: "ID", width: 65, columnGroup: "Project" },
79
+ { id: "name", name: "Project", width: 240, columnGroup: "Project", editor: TextEditor },
80
+ { id: "team", name: "Team", width: 140, columnGroup: "Project" },
81
+ { id: "hours", name: "Hours", width: 120, columnGroup: "Costing", headerCssClass: "right", formatter: NumericFormatter, format: "#,0.00", editor: NumericEditor },
82
+ { id: "rate", name: "Hourly rate", width: 135, columnGroup: "Costing", headerCssClass: "right", formatter: NumericFormatter, format: "#,0.00", editor: NumericEditor },
83
+ { id: "cost", name: "Estimated cost", width: 160, columnGroup: "Costing", headerCssClass: "right", formatter: NumericFormatter, format: "#,0.00", readonly: true, toolTip: "Calculated: hours × hourly rate" },
84
+ { id: "date", name: "Start date", width: 140, columnGroup: "Schedule", formatter: DateFormatter }
85
+ ];
86
+ const costing = new GridComponent(costingData, costingColumns, {
87
+ primaryKey: "id", editable: true, autoEdit: false, showColumnGroups: true,
88
+ preHeaderPanelHeight: 37, createFooterRow: true, showFooterRow: true, footerRowHeight: 36,
89
+ enableColumnReorder: false
90
+ }, "380px");
91
+ costing.initEvents(grid => {
92
+ grid.addFormula("cost", item => (item.hours || 0) * (item.rate || 0), ["hours", "rate"]);
93
+ grid.triggerAllFormulas();
94
+ const updateTotals = () => {
95
+ const label = grid.getFooterRowColumn("name");
96
+ if (label) { label.textContent = "Total"; }
97
+ for (const field of ["hours", "cost"] as const) {
98
+ const footer = grid.getFooterRowColumn(field);
99
+ if (footer) {
100
+ footer.classList.add("right", "tabular-nums");
101
+ footer.textContent = numeric.format(grid.getData().reduce((sum, item) => sum + item[field], 0), "#,0.00");
102
+ }
103
+ }
104
+ };
105
+ grid.onAfterEdit.subscribe(updateTotals);
106
+ grid.onFooterRowCellRendered.subscribe(updateTotals);
107
+ updateTotals();
108
+ });
109
+
110
+ const orderStatus: IJSLVNode = { tag: "p", content: "Drag a handle to change the project order.", attr: { role: "status", class: "sample-status" } };
111
+ const reorder = new GridComponent(data(15), [
112
+ { id: "_move", name: "", width: 36, minWidth: 36, maxWidth: 36, resizable: false, selectable: false,
113
+ behavior: "selectAndMove", cssClass: "grid-row-handle", toolTip: "Drag to reorder", formatter: () => '<span aria-hidden="true">⠿</span>' },
114
+ { id: "priority", name: "Order", width: 75, headerCssClass: "center", formatter: row => ({ text: String(row + 1), addClasses: ["center", "tabular-nums"] }) },
115
+ ...columns().filter(column => column.id !== "id").map(column => ({ ...column, sortable: false }))
116
+ ], { primaryKey: "id", allowRowMove: true, enableColumnReorder: false }, "320px");
117
+ reorder.initEvents(grid => grid.dataView.onRowsChanged.subscribe(() => {
118
+ orderStatus.content = "Project order: " + grid.getData().map(item => item.id).join(", ") + ".";
119
+ JSLRender.lastCreatedRenderer?.refresh();
120
+ }));
121
+ export const gridShowcase: IJSLVNode = {
122
+ tag: "section", attr: { id: "grids", class: "component-section", "aria-labelledby": "grids-title" }, children: [
123
+ { tag: "div", attr: { class: "hero-copy" }, children: [
124
+ { tag: "p", content: "THE ESSENTIALS / 22", attr: { class: "eyebrow" } },
125
+ { tag: "h2", content: "Grids", attr: { id: "grids-title", class: "section-title" } },
126
+ text("p", "Representative data, editing, grouping, and scrolling examples for the grid redesign.")
127
+ ] },
128
+ { tag: "div", attr: { class: "grid-samples" }, children: [
129
+ card("grid-basic", "Browse and sort", "30 projects. Click a heading to sort, then Shift-click other headings to add sort levels. Numbers show sorting priority. Select rows and resize or reorder columns.", basic),
130
+ card("grid-editable", "Edit in place", "Double-click an editable cell or press F2. Enter commits and Escape cancels. Editors retain their existing behavior and styling in this step.", editable, [edits]),
131
+ card("grid-grouped", "Grouped projects", "45 projects grouped by team. Expand and collapse groups independently.", grouped, [
132
+ { tag: "div", attr: { class: "sample-row" }, children: [
133
+ action("Collapse groups", () => { grouped.getRef().then(grid => grid.dataView.collapseAllGroups()); }),
134
+ action("Expand groups", () => { grouped.getRef().then(grid => grid.dataView.expandAllGroups()); })
135
+ ] }
136
+ ]),
137
+ card("grid-frozen", "Frozen columns", "12 columns and 120 projects. The ID and project stay visible while you scroll through ownership, status, dates, hours, and office details, even on wide screens.", frozen),
138
+ card("grid-large", "A larger dataset", "10,000 rows with an explicit rowHeight of 28 px. This example also checks that applications can retain the original density.", large),
139
+ card("grid-costing", "Column groups, formulas & totals", "Project, Costing, and Schedule headers group related columns. Double-click Hours or Hourly rate to edit: the grid formula recalculates Estimated cost, and the footer updates the totals.", costing, [
140
+ { tag: "pre", content: 'grid.addFormula("cost", item => item.hours * item.rate, ["hours", "rate"]);', attr: { class: "sample-code" } }
141
+ ]),
142
+ card("grid-reorder", "Arrange project priorities", "Drag a handle in the first column to move a project. The Order column follows its position while project IDs remain unchanged. Sorting is disabled here so it does not override the manual order.", reorder, [orderStatus])
143
+ ] },
144
+ { tag: "details", attr: { class: "usage" }, children: [
145
+ text("summary", "Grid showcase scope"),
146
+ text("p", 'NumericFormatter supplies right-aligned cells; NumericFormatter and DateFormatter apply tabular numerals. Set headerCssClass: "right" or "center" explicitly to match a column’s content. Header alignment is never inferred from its formatter.'),
147
+ text("p", "These examples use the existing GridComponent constructor, column definitions, editors, and DataView APIs. The default row height is 36 px; rowHeight can retain a different density. Core presentation follows the shared theme and inherits the host's typography. Editors, filter dialogs, menus, and lifecycle behavior are reserved for later steps.")
148
+ ] }
149
+ ]
150
+ };
@@ -0,0 +1,60 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="description" content="An interactive showcase of the redesigned JSL UI components.">
7
+ <title>JSL UI Showcase</title>
8
+ <link rel="stylesheet" href="./showcase.css">
9
+ </head>
10
+ <body>
11
+ <a class="skip-link" href="#main">Skip to components</a>
12
+ <aside class="sidebar" aria-label="Component navigation">
13
+ <a class="brand" href="#main"><span class="brand-mark">j.</span><span>JSL UI<span class="brand-caption">Component showcase</span></span></a>
14
+ <p class="nav-label">COMPONENTS <span>22</span></p>
15
+ <nav>
16
+ <a class="nav-item" href="#buttons" aria-current="location"><span class="nav-icon" aria-hidden="true">▣</span> Buttons <span class="nav-number">01</span></a>
17
+ <a class="nav-item" href="#dialogs"><span class="nav-icon" aria-hidden="true">▢</span> Dialogs <span class="nav-number">02</span></a>
18
+ <a class="nav-item" href="#tabs"><span class="nav-icon" aria-hidden="true">▤</span> Tabs <span class="nav-number">03</span></a>
19
+ <a class="nav-item" href="#sliders"><span class="nav-icon" aria-hidden="true">↔</span> Sliders <span class="nav-number">04</span></a>
20
+ <a class="nav-item" href="#dates"><span class="nav-icon" aria-hidden="true">▦</span> Dates <span class="nav-number">05</span></a>
21
+ <a class="nav-item" href="#date-ranges"><span class="nav-icon" aria-hidden="true">↔</span> Date ranges <span class="nav-number">06</span></a>
22
+ <a class="nav-item" href="#date-times"><span class="nav-icon" aria-hidden="true">◷</span> Date &amp; time <span class="nav-number">07</span></a>
23
+ <a class="nav-item" href="#selects"><span class="nav-icon" aria-hidden="true">⌄</span> Selects <span class="nav-number">08</span></a>
24
+ <a class="nav-item" href="#checkboxes"><span class="nav-icon" aria-hidden="true">☑</span> Checkboxes <span class="nav-number">09</span></a>
25
+ <a class="nav-item" href="#expanders"><span class="nav-icon" aria-hidden="true">›</span> Expanders <span class="nav-number">10</span></a>
26
+ <a class="nav-item" href="#inputs"><span class="nav-icon" aria-hidden="true">⌨</span> Inputs <span class="nav-number">11</span></a>
27
+ <a class="nav-item" href="#numbers"><span class="nav-icon" aria-hidden="true">#</span> Numbers <span class="nav-number">12</span></a>
28
+ <a class="nav-item" href="#form-fields"><span class="nav-icon" aria-hidden="true">▤</span> Form fields <span class="nav-number">13</span></a>
29
+ <a class="nav-item" href="#notifications"><span class="nav-icon" aria-hidden="true">!</span> Notifications <span class="nav-number">14</span></a>
30
+ <a class="nav-item" href="#loading"><span class="nav-icon" aria-hidden="true">◌</span> Loading <span class="nav-number">15</span></a>
31
+ <a class="nav-item" href="#navigation"><span class="nav-icon" aria-hidden="true">☰</span> Navigation <span class="nav-number">16</span></a>
32
+ <a class="nav-item" href="#progress"><span class="nav-icon" aria-hidden="true">&#9648;</span> Progress <span class="nav-number">17</span></a>
33
+ <a class="nav-item" href="#stack-panels"><span class="nav-icon" aria-hidden="true">&#9707;</span> Stack panels &amp; splitters <span class="nav-number">18</span></a>
34
+ <a class="nav-item" href="#trees"><span class="nav-icon" aria-hidden="true">&#8627;</span> Tree views <span class="nav-number">19</span></a>
35
+ <a class="nav-item" href="#overlays"><span class="nav-icon" aria-hidden="true">&#9703;</span> Overlays <span class="nav-number">20</span></a>
36
+ <a class="nav-item" href="#ribbons"><span class="nav-icon" aria-hidden="true">&#9637;</span> Ribbon menus <span class="nav-number">21</span></a>
37
+ <a class="nav-item" href="#grids"><span class="nav-icon" aria-hidden="true">&#9638;</span> Grids <span class="nav-number">22</span></a>
38
+ </nav>
39
+ <div class="sidebar-note"><span class="status-dot"></span> A growing collection<p>Redesigned components will be added here as the library evolves.</p></div>
40
+ </aside>
41
+ <main id="main" tabindex="-1">
42
+ <div class="page-meta">
43
+ <span>LIBRARY / COMPONENTS</span>
44
+ <div class="page-tools">
45
+ <label class="theme-picker" for="showcase-culture" title="Changing culture reloads and resets the examples with the selected date and number formats.">Culture
46
+ <select id="showcase-culture"><option value="">Default (ISO)</option></select>
47
+ </label>
48
+ <span id="showcase-format-preview" class="format-preview" aria-label="Date and number format example"></span>
49
+ <label class="theme-picker" for="showcase-theme">Theme
50
+ <select id="showcase-theme"><option value="default">Default (neutral)</option></select>
51
+ </label>
52
+ <span class="version">JSL UI <span class="status-dot"></span> Design preview</span>
53
+ </div>
54
+ </div>
55
+ <div id="showcase"></div>
56
+ <footer class="page-footer"><span>JSL UI · Component showcase</span><span>Built for everyday work.</span></footer>
57
+ </main>
58
+ <script type="module" src="./showcase.ts"></script>
59
+ </body>
60
+ </html>
@@ -0,0 +1,47 @@
1
+ import { IJSLComponent, IJSLVNode } from "jsl-render";
2
+ import { ProgressComponent } from "../../components/progress/ProgressComponent";
3
+ import { StackPanelComponent } from "../../components/stack-panel/StackPanelComponent";
4
+ import { SplitterComponent } from "../../components/stack-panel/SplitterComponent";
5
+ import { TreeViewComponent } from "../../components/tree-view/TreeViewComponent";
6
+ import { OverlayComponent } from "../../components/overlay/OverlayComponent";
7
+ import { RibbonMenuComponent } from "../../components/ribbon-menu/RibbonMenuComponent";
8
+ import { ButtonComponent } from "../../components/button/ButtonComponent";
9
+ type Content=IJSLVNode|IJSLComponent;
10
+ const text=(tag:string,content:string):IJSLVNode=>({tag,content});
11
+ const group=(children:Content[],className?:string):IJSLVNode=>({tag:"div",attr:{class:className},children});
12
+ const button=(content:string,click:()=>void)=>new ButtonComponent({content,click,type:"button",outlined:true,size:"small"});
13
+ const panel=(title:string,copy:string)=>group([text("h3",title),text("p",copy)],"layout-demo-panel");
14
+ function section(id:string,number:string,title:string,copy:string,children:Content[],usage:string):IJSLVNode {
15
+ return {tag:"section",attr:{id,class:"component-section","aria-labelledby":id+"-title"},children:[
16
+ group([text("p","THE ESSENTIALS / "+number),{tag:"h2",content:title,attr:{id:id+"-title",class:"section-title"}},text("p",copy)],"hero-copy"),
17
+ group(children,"sample-card remaining-demo"),
18
+ {tag:"details",attr:{class:"usage"},children:[text("summary",title+" usage & compatibility"),text("p",usage)]}
19
+ ]};
20
+ }
21
+ const progress=new ProgressComponent(45,{showPercent:true,ariaLabel:"Upload progress"});
22
+ export const progressShowcase=section("progress","17","Progress","A clear measure of how far a task has come.",[
23
+ progress,group([button("Advance",()=>progress.setProgress(Math.min(100,progress.getProgress()+10))),button("Reset",()=>progress.setProgress(0))],"sample-row"),
24
+ new ProgressComponent(72,{ariaLabel:"Processing progress"}),text("p","A slim indicator without a percentage label.")
25
+ ],"The numeric constructor, color override, formatting, getProgress(), and setProgress() remain available. Displayed values are clamped to 0–100. ariaLabel names the progress bar.");
26
+ export const stackShowcase=section("stack-panels","18","Stack panels & splitters","Comfortable layouts with a small, precise resize handle.",[
27
+ text("h3","Side by side"),new StackPanelComponent([panel("Library","Drag the handle or focus it and use the arrow keys."),new SplitterComponent({minSizesInPX:[90,90]}),panel("Workspace","Shift + arrow moves faster. Home and End move to the allowed limits.")],{sizes:["40%",undefined,"*"],style:"height:220px;border:1px solid var(--jsl-color-border);border-radius:.5rem;overflow:hidden"}),
28
+ text("h3","One above another"),new StackPanelComponent([panel("Preview","Vertical layouts resize by height."),new SplitterComponent({minSizesInPX:[60,60]}),panel("Details","The completion callback reports the first panel's height in this layout.")],{orientation:"vertical",sizes:["45%",undefined,"*"],style:"height:300px;border:1px solid var(--jsl-color-border);border-radius:.5rem;overflow:hidden"}),
29
+ text("h3","Responsive layout"),new StackPanelComponent([panel("First","Panels stack on smaller screens."),panel("Second","No resize handle is needed here.")],{responsive:true})
30
+ ],"Existing orientation, sizes, valign, wrap, responsive, and flexForChildren options remain. Splitter preserves min/max sizes and resize callbacks, and supports mouse, touch, pen, and keyboard. Responsive horizontal panels stack below 70em; splitters hide in that layout.");
31
+ const treeStatus=text("p","Select a node to explore.");treeStatus.attr={role:"status"};
32
+ const makeTree=()=>({id:"workspace",content:"Workspace",children:[{id:"projects",content:"Projects",children:[{id:"design",content:"Design system"},{id:"website",content:"Website refresh"}]},{id:"archive",content:"Archive",collapsed:true,children:[{id:"last-year",content:"Last year's work"}]},{id:"notes",content:"Notes"}]});
33
+ export const treeShowcase=section("trees","19","Tree views","A calm hierarchy with clear expansion and selection states.",[
34
+ new TreeViewComponent({data:makeTree(),ariaLabel:"Workspace files",onSelect:item=>{treeStatus.content="Selected: "+item.id;}}),treeStatus,
35
+ text("h3","Custom row content"),new TreeViewComponent({data:makeTree(),ariaLabel:"Rich file tree",renderItem:item=>group([text("span",item.children?.length ? "▱" : "·"),text("span",String(item.content))],"tree-demo-content")})
36
+ ],"The data, collapsed, content, and renderItem options remain. Expansion updates collapsed on the data node. onSelect and ariaLabel are optional additions. Use arrows, Home/End, Enter, or Space; node IDs are data identifiers and do not collide across trees.");
37
+ const overlayCopy=()=>group([text("h2","Workspace settings"),text("p","A little room for secondary tasks, without leaving your current view."),new ButtonComponent({content:"Save settings",type:"button",click:()=>overlay.close()})]);
38
+ let overlay:OverlayComponent;
39
+ let allowClose=false;
40
+ export const overlayShowcase=section("overlays","20","Overlays","Focused panels that slide in from the side or the top.",[
41
+ group([button("Open side panel",()=>{overlay=OverlayComponent.show({content:overlayCopy(),ariaLabel:"Workspace settings",size:"26rem"});}),button("Open top panel",()=>{overlay=OverlayComponent.show({content:overlayCopy(),direction:"top",size:"20rem",ariaLabel:"Workspace settings"});}),button("Try close guard",()=>{allowClose=false;overlay=OverlayComponent.show({ariaLabel:"Protected panel",onClose:()=>allowClose,content:group([text("h2","A protected panel"),text("p","Closing is blocked until you choose Allow close."),button("Allow close",()=>{allowClose=true;overlay.close();})])});})],"sample-row")
42
+ ],"show(), close(), content, direction, size, animation, dismissal flags, and callbacks remain. onClose returning false vetoes dismissal. Native modal dialogs contain keyboard focus, support nested panels, and restore focus on close. ariaLabel and closeButtonLabel provide accessible names.");
43
+ const ribbonStatus=text("p","Choose an action.");ribbonStatus.attr={role:"status"};
44
+ const ribbonAction=(content:string)=>button(content,()=>{ribbonStatus.content=content+" selected.";});
45
+ export const ribbonShowcase=section("ribbons","21","Ribbon menus","Related actions grouped in a flexible, themed toolbar.",[
46
+ new RibbonMenuComponent([{title:"Project",children:[ribbonAction("New"),ribbonAction("Open"),ribbonAction("Save")]},{title:"Edit",children:[ribbonAction("Undo"),ribbonAction("Redo")]},{title:"View",children:[new ButtonComponent({content:"Details",toggle:true,outlined:true,type:"button"}),new ButtonComponent({content:"Unavailable",enabled:false,type:"button"})]}]),ribbonStatus
47
+ ],"The groups constructor and group title/children interface remain unchanged. Groups wrap with available space and use the normal ButtonComponent styles, including toggles and disabled actions.");