@nebularstreams/libmui 3.0.8 → 3.1.1

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 (2) hide show
  1. package/package.json +2 -2
  2. package/readme.md +602 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nebularstreams/libmui",
3
- "description": "Micro UI based on Mithril",
4
- "version": "3.0.8",
3
+ "description": "Lightweight, high-performance Mithril widget library for modern websites and JavaScript applications.",
4
+ "version": "3.1.1",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "scripts": {
package/readme.md ADDED
@@ -0,0 +1,602 @@
1
+ # LibMui
2
+
3
+ LibMui is a lightweight, high-performance widget library for modern websites and JavaScript applications. It is built on [Mithril](https://mithril.js.org/) and provides responsive controls, selectors, dialogs, color tools, file pickers, parameter editors, and reusable layout components.
4
+
5
+ The package ships its widget styles and Font Awesome icon definitions with the JavaScript entry point, so importing LibMui also loads its visual system.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @nebularstreams/libmui mithril
11
+ ```
12
+
13
+ ```js
14
+ import m from "mithril";
15
+ import {
16
+ ButtonNew,
17
+ InputWidget,
18
+ RangeWidget,
19
+ SwitchWidget
20
+ } from "@nebularstreams/libmui";
21
+ ```
22
+
23
+ LibMui widgets are Mithril components. Render them with `m(Component, attrs, children)` and keep application values in your own state.
24
+
25
+ ## Quick start
26
+
27
+ ```js
28
+ import m from "mithril";
29
+ import {
30
+ ButtonNew,
31
+ InputWidget,
32
+ RangeWidget,
33
+ SwitchWidget
34
+ } from "@nebularstreams/libmui";
35
+
36
+ const state = {
37
+ name: "Nebular",
38
+ volume: 0.5,
39
+ enabled: true
40
+ };
41
+
42
+ const Demo = {
43
+ view: () => m("main.pad-3", [
44
+ m(InputWidget, {
45
+ label: "Display name",
46
+ value: state.name,
47
+ hint: "Enter a name",
48
+ onchange: value => state.name = value
49
+ }),
50
+
51
+ m(RangeWidget, {
52
+ title: "Volume",
53
+ value: state.volume,
54
+ min: 0,
55
+ max: 1,
56
+ step: 0.01,
57
+ onchange: value => state.volume = value
58
+ }),
59
+
60
+ m(SwitchWidget, {
61
+ text: "Enabled",
62
+ checked: state.enabled,
63
+ onchange: checked => state.enabled = checked
64
+ }),
65
+
66
+ m(ButtonNew, {
67
+ icon: "check",
68
+ text: "Save",
69
+ onClick: () => saveSettings(state)
70
+ })
71
+ ])
72
+ };
73
+
74
+ m.mount(document.body, Demo);
75
+ ```
76
+
77
+ > `Button` uses an `onclick` callback, while the newer `ButtonNew` uses `onClick`.
78
+
79
+ ## Buttons and icons
80
+
81
+ ### `Icon`
82
+
83
+ Renders a Font Awesome icon.
84
+
85
+ ```js
86
+ m(Icon, {
87
+ icon: "gear",
88
+ size: 2,
89
+ icontitle: "Settings",
90
+ onclick: openSettings
91
+ });
92
+ ```
93
+
94
+ | Attribute | Description |
95
+ |-------------|---------------------------------------------------------------------|
96
+ | `icon` | Font Awesome icon name without the `fa-` prefix. Defaults to `cog`. |
97
+ | `pack` | Icon pack class. Defaults to `fas`. |
98
+ | `size` | Font Awesome size multiplier. |
99
+ | `icontitle` | Native tooltip text. |
100
+ | `iconClass` | Additional classes for the icon element. |
101
+ | `onclick` | Optional icon click handler. |
102
+
103
+ ### `ButtonNew`
104
+
105
+ Renders a button, link, or download action. Promise-returning handlers automatically apply a busy state and expose rejected errors through `data-error`.
106
+
107
+ ```js
108
+ m(ButtonNew, {
109
+ icon: "cloud-upload-alt",
110
+ text: "Publish",
111
+ title: "Publish changes",
112
+ class: "accentbutton",
113
+ onClick: () => publishProject()
114
+ });
115
+ ```
116
+
117
+ Important attributes are `icon`, `text`, `title`, `class`, `textClass`, `right`, `onClick`, `link`, `target`, and `download`. When `link` or `download` is present, the widget renders an anchor instead of a button.
118
+
119
+ `Button` offers the same basic presentation but uses `onclick`.
120
+
121
+ ### `ButtonChoiceWidget`
122
+
123
+ Displays a set of choices as a row of buttons.
124
+
125
+ ```js
126
+ m(ButtonChoiceWidget, {
127
+ label: "Quality",
128
+ value: state.quality,
129
+ choices: {
130
+ low: {value: "low", caption: "Low"},
131
+ high: {value: "high", caption: "High", icon: "star"}
132
+ },
133
+ onchange: choice => state.quality = choice.value
134
+ });
135
+ ```
136
+
137
+ Use `choose: ["one", "two"]` for a simple list. Use `choices` for captions, icons, classes, and distinct values. Set `toggle` to allow the active choice to be cleared, `field` to compare a property of each choice, or `render(choice)` for custom button content.
138
+
139
+ ## Text and numeric input
140
+
141
+ ### `InputWidget`
142
+
143
+ Provides single-line, multiline, numeric, validated, read-only, and editable-static inputs.
144
+
145
+ ```js
146
+ m(InputWidget, {
147
+ label: "Retries",
148
+ type: "number",
149
+ value: state.retries,
150
+ min: 0,
151
+ max: 10,
152
+ step: 1,
153
+ onchange: value => state.retries = value
154
+ });
155
+ ```
156
+
157
+ | Attribute | Description |
158
+ |-------------------------------------|-------------------------------------------------------------------------------|
159
+ | `value` | Current controlled value. |
160
+ | `onchange(value, event, multiline)` | Called when the value is committed. |
161
+ | `oninput(value, event)` | Optional live-input callback. |
162
+ | `type` | Native input type. Use `number` for parsed numeric values. |
163
+ | `float` | Parses numeric input with `parseFloat` instead of `parseInt`. |
164
+ | `hint` | Placeholder text. |
165
+ | `label` | Accessible label applied to the container. |
166
+ | `multi` | Adds a control for switching between input and textarea. |
167
+ | `alwaysmulti` | Always renders a textarea. |
168
+ | `validator(value)` | Returns whether the current value is valid. |
169
+ | `readonly` | Prevents editing. |
170
+ | `maxlength`, `min`, `max`, `step` | Native input constraints. |
171
+ | `action`, `actions` | One or more `ButtonNew` configurations shown beside the input. |
172
+ | `onenter(value, event)` | Called when Enter is pressed. |
173
+ | `staticField` | Displays a static value until the user activates it. |
174
+ | `draginc` | Enables vertical drag adjustment by this increment on a static numeric field. |
175
+
176
+ ### `RangeWidget`
177
+
178
+ Renders an accessible slider with a live value display.
179
+
180
+ ```js
181
+ m(RangeWidget, {
182
+ title: "Opacity",
183
+ value: state.opacity,
184
+ min: 0,
185
+ max: 1,
186
+ step: 0.01,
187
+ parser: value => `${Math.round(value * 100)}%`,
188
+ onchange: (value, dragging) => state.opacity = value
189
+ });
190
+ ```
191
+
192
+ Use `multiplier` to let the range expand beyond its initial limits, `relative` for relative movement, and `factor` to scale interaction. The second `onchange` argument indicates whether the slider is being dragged.
193
+
194
+ ### `PlusMinus`
195
+
196
+ Provides decrement and increment buttons around a displayed value.
197
+
198
+ ```js
199
+ m(PlusMinus, {
200
+ title: "Copies",
201
+ value: state.copies,
202
+ min: 1,
203
+ max: 20,
204
+ step: 1,
205
+ onchange: value => state.copies = value
206
+ });
207
+ ```
208
+
209
+ Customize the display with `parser(value)` and the buttons with `iconMinus`, `iconPlus`, and `buttonClass`.
210
+
211
+ ### `SwitchWidget`
212
+
213
+ Displays a controlled toggle.
214
+
215
+ ```js
216
+ m(SwitchWidget, {
217
+ text: "Show guides",
218
+ subline: "Display alignment helpers",
219
+ checked: state.guides,
220
+ right: true,
221
+ onchange: checked => state.guides = checked
222
+ });
223
+ ```
224
+
225
+ ## Selection widgets
226
+
227
+ ### `ComboWidget`
228
+
229
+ Renders a native select from `{name, value}` items.
230
+
231
+ ```js
232
+ m(ComboWidget, {
233
+ caption: "Renderer",
234
+ value: state.renderer,
235
+ items: [
236
+ {name: "WebGL", value: "webgl"},
237
+ {name: "WebGPU", value: "webgpu"}
238
+ ],
239
+ onchange: (value, item, index) => state.renderer = value
240
+ });
241
+ ```
242
+
243
+ Pass `groups` instead of `items` to create named option groups. `parser(value)` can normalize values before matching the controlled selection.
244
+
245
+ ### `ComboPrompt`
246
+
247
+ Shows the current choice as a compact button and opens a modal choice menu.
248
+
249
+ ```js
250
+ m(ComboPrompt, {
251
+ label: "Theme",
252
+ value: state.theme,
253
+ items: themes,
254
+ field: "name",
255
+ emptyText: "Choose a theme",
256
+ onchange: value => state.theme = value
257
+ });
258
+ ```
259
+
260
+ Useful attributes include `items`, `field`, `itemsText`, `icon`, `emptyIcon`, `emptyText`, `locked`, `nodelete`, `confirm`, `textNew`, and `textHint`. Shift-clicking the main button clears the value.
261
+
262
+ ### `SmileysButton`
263
+
264
+ Opens LibMui's application-level icon and emoji selector. The selected entry is returned through `onchange`.
265
+
266
+ ```js
267
+ m(SmileysButton, {
268
+ icon: state.markerIcon,
269
+ icons: availableIcons,
270
+ onchange: icon => state.markerIcon = icon
271
+ });
272
+ ```
273
+
274
+ The current `icon` may contain an `icon` field for a Font Awesome symbol, a `glyph` field for an emoji, or both. This widget uses `MainState` to open the `emoji` application panel, so that panel must be registered by the host application.
275
+
276
+ #### Optional emoji picker package
277
+
278
+ Install `@nebularstreams/libmui-extensions` when your application needs a ready-made emoji picker:
279
+
280
+ ```bash
281
+ npm install @nebularstreams/libmui-extensions
282
+ ```
283
+
284
+ The optional package provides `SmileysWidget`, a searchable picker organized into emoji categories. It can also include Font Awesome symbols and remembers selected emoji as favorites during the current session.
285
+
286
+ ```js
287
+ import m from "mithril";
288
+ import {SmileysWidget} from "@nebularstreams/libmui-extensions";
289
+
290
+ m(SmileysWidget, {
291
+ icons: true,
292
+ onClick: (glyph, item) => {
293
+ state.marker = {
294
+ glyph,
295
+ icon: item.icon,
296
+ name: item.name
297
+ };
298
+ }
299
+ });
300
+ ```
301
+
302
+ | Attribute | Description |
303
+ | --- | --- |
304
+ | `onClick(glyph, item)` | Called with the selected character and its metadata. |
305
+ | `icons` | Includes Font Awesome symbols alongside emoji. |
306
+ | `noemojis` | Hides emoji and shows only the optional icon set. |
307
+ | `horizontal` | Places the category navigation above the picker instead of beside it. |
308
+ | `mainclass` | Replaces the picker's default root class. |
309
+
310
+ The package also exports `SmileysPanel`, which wraps the picker in a horizontal panel with a title bar and optional back action:
311
+
312
+ ```js
313
+ import {SmileysPanel} from "@nebularstreams/libmui-extensions";
314
+
315
+ m(SmileysPanel, {
316
+ icons: true,
317
+ onClick: (glyph, item) => selectMarker(glyph, item),
318
+ onClose: closePicker
319
+ });
320
+ ```
321
+
322
+ Use `SmileysWidget` when embedding the picker in your own layout. Use `SmileysPanel` when you want a complete application panel. The extension package is optional; the rest of LibMui does not require it.
323
+
324
+ ### `Selector`
325
+
326
+ Builds a toolbar that switches between complete Mithril components.
327
+
328
+ ```js
329
+ const panels = [
330
+ {text: "General", icon: "sliders-h", widget: GeneralPanel},
331
+ {text: "Advanced", icon: "cogs", widget: AdvancedPanel}
332
+ ];
333
+
334
+ m(Selector, {
335
+ title: "Settings",
336
+ items: panels,
337
+ selected: 0,
338
+ toggle: true,
339
+ closeButton: true,
340
+ onSelected: (item, params, changed) => {
341
+ if (changed) console.log("Selected", item);
342
+ }
343
+ });
344
+ ```
345
+
346
+ Each item accepts `text`, `icon`, `title`, `class`, `widget`, `attrs`, and `children`. Widget `attrs` may be an object or a function receiving the selector's own attributes. Pass a shared `state` object when another component needs to call `state.setItem(index, ...params)`.
347
+
348
+ ## Layout and status
349
+
350
+ ### `CardieWidget`
351
+
352
+ Creates a collapsible card whose body can be supplied as children or through `expandedView(attrs, state)`.
353
+
354
+ ```js
355
+ m(CardieWidget, {
356
+ cardid: "settings-audio",
357
+ title: "Audio settings",
358
+ collapsed: true,
359
+ expandedView: () => m(AudioSettings)
360
+ });
361
+ ```
362
+
363
+ Cards sharing the prefix before the first `-` in `cardid` behave like an accordion. Use `nofold` for a permanently expanded card, `action` and `collapsedAction` for title actions, and `alwaysView` for content that remains outside the folding body.
364
+
365
+ ### `TitleBarWidget`
366
+
367
+ Creates an application or panel title bar with optional back navigation, logo, editable title, actions, and overflow menu.
368
+
369
+ ```js
370
+ m(TitleBarWidget, {
371
+ title: "Project settings",
372
+ onClick: goBack,
373
+ action: {icon: "save", title: "Save", onClick: save},
374
+ overflow: {
375
+ items: [
376
+ {caption: "Duplicate", icon: "copy", onClick: duplicate},
377
+ {caption: "Delete", icon: "trash", onClick: remove}
378
+ ]
379
+ }
380
+ });
381
+ ```
382
+
383
+ ### `Separator`
384
+
385
+ Renders a labelled section divider and optional child actions.
386
+
387
+ ```js
388
+ m(Separator, {text: "Appearance"});
389
+ ```
390
+
391
+ ### `NoticeWidget`
392
+
393
+ Renders inline informational content with an optional icon, click handler, and child content.
394
+
395
+ ```js
396
+ m(NoticeWidget, {
397
+ icon: "info-circle",
398
+ text: "Changes are saved automatically."
399
+ });
400
+ ```
401
+
402
+ ### Empty states
403
+
404
+ `Empty` renders a centered icon, message, and optional children. `EmptyButton` adds a primary action, while `CoolerEmpty` supports a header and one or more action definitions.
405
+
406
+ ```js
407
+ m(CoolerEmpty, {
408
+ icon: "folder-open",
409
+ header: "No projects",
410
+ message: "Create your first project to begin.",
411
+ action: {caption: "Create project", onclick: createProject}
412
+ });
413
+ ```
414
+
415
+ ## Color widgets
416
+
417
+ ### `ColorJoeWidget`
418
+
419
+ Displays a color swatch that opens an RGB or HSL picker in a modal.
420
+
421
+ ```js
422
+ m(ColorJoeWidget, {
423
+ caption: "Background",
424
+ color: state.color,
425
+ onchange: (cssColor, colorObject) => state.color = cssColor
426
+ });
427
+ ```
428
+
429
+ Set `hsl` for an HSL picker, `noalpha` to remove alpha controls, and `onshiftclick` to provide an alternate swatch action. Passing `null` from the picker indicates removal.
430
+
431
+ ### `ColorJoePack`
432
+
433
+ Edits an array of RGB(A) or HSL(A) colors and supports up to eight entries by default.
434
+
435
+ ```js
436
+ m(ColorJoePack, {
437
+ colors: state.palette,
438
+ max: 8,
439
+ onchange: colors => state.palette = colors
440
+ });
441
+ ```
442
+
443
+ Click a swatch to edit it, Shift-click one to remove it, click **+** to add a color, or Shift-click **+** to clear the collection.
444
+
445
+ ## File and image widgets
446
+
447
+ ### `FileSelectButton`
448
+
449
+ ```js
450
+ m(FileSelectButton, {
451
+ text: "Choose JSON",
452
+ icon: "upload",
453
+ accept: "application/json",
454
+ onFile: file => importFile(file)
455
+ });
456
+ ```
457
+
458
+ ### Image helpers
459
+
460
+ - `ImageSelectButton` returns selected files through `onImageFiles(files)`.
461
+ - `ImageProcessButton` loads the selected image and passes an `HTMLImageElement` to `onImage(image)`.
462
+ - `ImageDataButton` reads the image as a data URL and calls `onImageUrl({name, url})`.
463
+ - `MediaSelectButton` opens LibMui's configured media-selection application state.
464
+ - `processImage(file)` provides the image-loading behavior as a standalone promise.
465
+
466
+ All image buttons accept the presentation attributes `class`, `icon`, `iconClass`, `text`, `textClass`, and `accept`. Processing buttons also support `onError(error)`.
467
+
468
+ ## Notifications and dialogs
469
+
470
+ Add these containers once near the root of the document when using notifications, prompts, color pickers, or modal utilities:
471
+
472
+ ```html
473
+
474
+ <div id="notification-container"></div>
475
+ <div id="modal-container" style="display: none"></div>
476
+ ```
477
+
478
+ ### Notifications
479
+
480
+ ```js
481
+ Notification.add("Saved", "success");
482
+ Notification.add("Could not connect", "error");
483
+ Notification.clear();
484
+ ```
485
+
486
+ Notifications are queued and dismissed automatically. Supported semantic types include `info`, `success`, and `error`; clicking the current notification advances the queue.
487
+
488
+ ### Confirmations
489
+
490
+ ```js
491
+ const result = await ConfirmPromise(
492
+ "Delete project?",
493
+ "This action cannot be undone.",
494
+ ["CANCEL", "DELETE"],
495
+ true
496
+ );
497
+
498
+ if (result.name === "DELETE") deleteProject();
499
+ ```
500
+
501
+ `ConfirmPromise` resolves to `{idx, name}`. The lowercase `confirmPromise` export provides the library's alternative confirmation helper. `popupMessage(content, cancelable, contentClass)` can render custom Mithril content in the modal container, while `clearModal()` closes the active modal.
502
+
503
+ ### `OverflowMenu`
504
+
505
+ ```js
506
+ const {action} = await OverflowMenu(
507
+ document.body,
508
+ {title: "Project actions"},
509
+ [
510
+ {caption: "Duplicate", icon: "copy"},
511
+ {caption: "Archive", icon: "archive"}
512
+ ]
513
+ );
514
+
515
+ console.log(action.caption);
516
+ ```
517
+
518
+ ## Tickers and collections
519
+
520
+ `GenericButtonTicker` renders a compact collection of selectable buttons with add, edit, update, rename, and delete behavior. `CoolGenericTicker` switches large collections to previous/next navigation. `ThematicWrapper` provides their themed frame.
521
+
522
+ ```js
523
+ m(GenericButtonTicker, {
524
+ items: state.presets,
525
+ current: state.currentPreset,
526
+ emptyText: "No presets",
527
+ addText: "Add preset",
528
+ onSelect: (item, index) => state.currentPreset = index,
529
+ onAdd: () => addPreset(),
530
+ onDelete: index => deletePreset(index)
531
+ });
532
+ ```
533
+
534
+ `CoolContentTicker` is the application-integrated variant. It expects a `layer` containing `items` and a `global.storyWrapper` implementing LibMui's ticker state methods.
535
+
536
+ ## Metadata-driven forms
537
+
538
+ `ParameterTable` and `TemplatedInputWidget` generate complete control panels from metadata and a mutable parameter object. They are intended for applications with many dynamic properties, presets, or schema-defined editors.
539
+
540
+ ```js
541
+ const parameters = {
542
+ title: "Scene one",
543
+ visible: true,
544
+ opacity: 0.8
545
+ };
546
+
547
+ const meta = {
548
+ title: {type: "text", title: "Title"},
549
+ visible: {type: "boolean", title: "Visible"},
550
+ opacity: {type: "number", title: "Opacity", min: 0, max: 1, step: 0.01}
551
+ };
552
+
553
+ m(ParameterTable, {
554
+ meta,
555
+ parameters,
556
+ onchange: (changeCode, target, key) => {
557
+ console.log("Changed", key, target[key]);
558
+ }
559
+ });
560
+ ```
561
+
562
+ Related widgets include:
563
+
564
+ - `ParameterCardie` — groups generated parameter controls in a collapsible card.
565
+ - `MultiToggleWidget` — edits a keyed set of toggle values.
566
+ - `RangeToggleWidget` — edits a keyed set of numeric range values.
567
+ - `TemplatedInputWidget` — combines a value editor with presets and optional property controls.
568
+
569
+ Metadata forms support standard strings, numbers, booleans, ranges, choices, colors, JSON, nested groups, separators, custom widgets, and injected asset or code editors. For application-specific editors, pass `AssetButton` or `CodeMirror` through the table attributes.
570
+
571
+ ## Application routing
572
+
573
+ `startApp(target, appMetadata)` starts a Mithril router on an element ID:
574
+
575
+ ```js
576
+ startApp("app", {
577
+ default: "/home",
578
+ routes: {
579
+ "/home": HomePage,
580
+ "/settings": SettingsPage
581
+ }
582
+ });
583
+ ```
584
+
585
+ ## Public exports
586
+
587
+ The package exports the following public widgets:
588
+
589
+ - Foundations: `Button`, `ButtonNew`, `Icon`, `Empty`, `Selector`, `Notification`
590
+ - Inputs: `InputWidget`, `RangeWidget`, `PlusMinus`, `SwitchWidget`
591
+ - Choices: `ButtonChoiceWidget`, `ComboWidget`, `ComboPrompt`, `SmileysButton`
592
+ - Layout: `CardieWidget`, `Separator`, `TitleBarWidget`, `NoticeWidget`, `EmptyButton`, `CoolerEmpty`
593
+ - Colors: `ColorJoeWidget`, `ColorJoePack`
594
+ - Files: `FileSelectButton`, `ImageSelectButton`, `ImageProcessButton`, `ImageDataButton`, `MediaSelectButton`
595
+ - Collections: `GenericButtonTicker`, `CoolGenericTicker`, `CoolContentTicker`, `ThematicWrapper`
596
+ - Dynamic forms: `TemplatedInputWidget`, `ParameterTable`, `ParameterCardie`, `MultiToggleWidget`, `RangeToggleWidget`
597
+
598
+ LibMui also exports modal, cloning, tokenization, file-path, routing, application-state, and widget-definition utilities. These support the widgets but are outside this widget-focused guide.
599
+
600
+ ## Browser support
601
+
602
+ LibMui is an ES module intended for modern browsers. Widgets that read local files require the FileReader API, while image processing also uses the browser `Image` API. The core rendering dependency is Mithril 2.x.