@hypericon/mico 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,3 +12,57 @@ UI components and utilities built with Mithril.
12
12
  ```sh
13
13
  $ npm i @hypericon/mico
14
14
  ```
15
+
16
+ ## Usage
17
+
18
+ **[View the usage and examples documentation here.](https://hypericon-mico.netlify.app)**
19
+
20
+ ### Example
21
+
22
+ Import the components from the package. Some components may also export functions to build typed versions. Components that have customisable styles have their CSS classes of config attributes documented on the documentation site
23
+
24
+ ```ts
25
+ import m from "mithril";
26
+ import { typedTable } from "@hypericon/mico";
27
+ import { Widget } from "../interfaces"; // Example
28
+ import { WidgetService } from "../services"; // Example
29
+ import { WidgetStatusIcon } from "../components"; // Example
30
+
31
+ const WidgetTable = typedTable<Widget>();
32
+
33
+ const ExamplePage: m.Component = {
34
+ view() {
35
+ return m("", [
36
+ m("h1", "Widgets"),
37
+ m(WidgetTable, {
38
+ data: WidgetService.widgets,
39
+ columns: [
40
+ WidgetTable.col("Position", (w, i) => i + 1), // Row index is supplied
41
+ WidgetTable.col("Name", w => w.name), // Type checking on row object properties
42
+ WidgetTable.col("Price", w => `£${w.price}`),
43
+ WidgetTable.col("Status", w => m(WidgetStatusIcon, { widget: w })),
44
+ // etc.
45
+ ],
46
+ }),
47
+ ]);
48
+ }
49
+ };
50
+ ```
51
+
52
+ ## Development
53
+
54
+ Start a hot-reloading dev server serving the documentation and example site with
55
+
56
+ ```sh
57
+ $ npm run dev
58
+ ```
59
+
60
+ Publish the updated package and deploy the latest version of the docs site with
61
+
62
+ ```sh
63
+ $ npm run publish-deploy
64
+ ```
65
+
66
+ ## License
67
+
68
+ MIT
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.dialogMgr = exports.DialogManager = void 0;
16
16
  const mithril_1 = __importDefault(require("mithril"));
17
+ const utils_1 = require("../../utils");
17
18
  class DialogManager {
18
19
  get isOpen() { return this.currentDialog !== undefined; }
19
20
  constructor(options) {
@@ -65,11 +66,7 @@ class DialogManager {
65
66
  // console.log(ev);
66
67
  if (ev.target !== dom)
67
68
  return;
68
- const dialogRect = dom.getBoundingClientRect();
69
- const isInDialog = (ev.clientY >= dialogRect.top) &&
70
- (ev.clientY <= dialogRect.bottom) &&
71
- (ev.clientX >= dialogRect.left) &&
72
- (ev.clientX <= dialogRect.right);
69
+ const isInDialog = (0, utils_1.mouseWithinElement)(ev, dom);
73
70
  if (!isInDialog)
74
71
  manager.close();
75
72
  });
@@ -0,0 +1,24 @@
1
+ import m from "mithril";
2
+ import { DropTargetState } from "./drop-target.state";
3
+ export declare const DropTarget: m.Component<{
4
+ /**
5
+ * Function called when the user drops one or more files onto the drop target
6
+ * @param result
7
+ * @returns
8
+ */
9
+ onDropFiles: (result: {
10
+ files: File[];
11
+ ev: DragEvent;
12
+ }) => any;
13
+ /** The message to display in the drop target */
14
+ dropMessage?: string | m.Children;
15
+ /**
16
+ * Whether to automatically set the height of the drop target baed on the height of the wrapped elements
17
+ * @default true
18
+ */
19
+ autoHeight?: boolean;
20
+ class?: string;
21
+ style?: string;
22
+ }, {
23
+ state: DropTargetState;
24
+ }>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DropTarget = void 0;
7
+ const mithril_1 = __importDefault(require("mithril"));
8
+ const drop_target_state_1 = require("./drop-target.state");
9
+ const utils_1 = require("../../utils");
10
+ exports.DropTarget = {
11
+ oninit() {
12
+ this.state = new drop_target_state_1.DropTargetState();
13
+ },
14
+ oncreate({ dom }) {
15
+ this.state.lastHeight = dom.clientHeight;
16
+ },
17
+ onupdate({ dom }) {
18
+ if (!this.state.isDragging) {
19
+ this.state.lastHeight = dom.clientHeight;
20
+ }
21
+ },
22
+ view({ attrs, children }) {
23
+ const autoHeightStr = (attrs.autoHeight !== false) ? `height: ${this.state.lastHeight}px;` : "";
24
+ return (0, mithril_1.default)(".mico-drop-container", {
25
+ class: attrs.class,
26
+ style: attrs.style,
27
+ }, [
28
+ this.state.isDragging ? [
29
+ (0, mithril_1.default)("", {
30
+ class: `mico-drop-target ${this.state.isOver ? "highlighted" : ""}`,
31
+ style: `${autoHeightStr} display: flex; flex-direction: column; align-items: center; justify-content: center;`,
32
+ ondragover: (ev) => {
33
+ this.state.isOver = true;
34
+ },
35
+ ondragleave: (ev) => {
36
+ const containsNewTarget = ev.currentTarget.contains(ev.relatedTarget);
37
+ if (!containsNewTarget) {
38
+ this.state.isOver = false;
39
+ }
40
+ ev.preventDefault();
41
+ return false;
42
+ },
43
+ ondrop: (ev) => {
44
+ // console.log("ondrop", ev);
45
+ this.state.isOver = false;
46
+ ev.preventDefault();
47
+ const files = (0, utils_1.dragEventFiles)(ev);
48
+ if (files.length > 0) {
49
+ attrs.onDropFiles({ ev, files });
50
+ }
51
+ },
52
+ }, [
53
+ (attrs.dropMessage && typeof attrs.dropMessage === "object")
54
+ ? attrs.dropMessage
55
+ : (0, mithril_1.default)("span.mico-drop-target-message", attrs.dropMessage || "Drop files here"),
56
+ ]),
57
+ ] : [
58
+ children,
59
+ ],
60
+ ]);
61
+ }
62
+ };
@@ -0,0 +1,9 @@
1
+ export declare class DropTargetState {
2
+ private subs;
3
+ isDragging: boolean;
4
+ isOver: boolean;
5
+ lastHeight: number;
6
+ constructor();
7
+ initialise(): void;
8
+ destroy(): void;
9
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DropTargetState = void 0;
4
+ const mithril_1 = require("mithril");
5
+ const rxjs_1 = require("rxjs");
6
+ class DropTargetState {
7
+ constructor() {
8
+ this.subs = new rxjs_1.Subscription();
9
+ this.isDragging = false;
10
+ this.isOver = false;
11
+ this.lastHeight = 100;
12
+ this.initialise();
13
+ }
14
+ initialise() {
15
+ this.destroy();
16
+ this.subs = new rxjs_1.Subscription();
17
+ this.isDragging = false;
18
+ this.isOver = false;
19
+ const onclick = () => {
20
+ this.isDragging = false;
21
+ (0, mithril_1.redraw)();
22
+ };
23
+ const ondragstart = (ev) => {
24
+ ev.preventDefault();
25
+ };
26
+ const ondragend = (ev) => {
27
+ this.isDragging = false;
28
+ (0, mithril_1.redraw)();
29
+ ev.preventDefault();
30
+ };
31
+ const ondragenter = (ev) => {
32
+ this.isDragging = true;
33
+ (0, mithril_1.redraw)();
34
+ ev.preventDefault();
35
+ };
36
+ const ondragover = (ev) => {
37
+ ev.preventDefault();
38
+ };
39
+ const ondragleave = (ev) => {
40
+ // if the drag moved from the element to outside the element
41
+ const containsTarget = ev.currentTarget.contains(ev.relatedTarget);
42
+ if (!containsTarget) {
43
+ // console.log("page ondragleave, isDragging=false")
44
+ this.isDragging = false;
45
+ (0, mithril_1.redraw)();
46
+ }
47
+ };
48
+ const ondrag = (ev) => {
49
+ ev.preventDefault();
50
+ };
51
+ const ondrop = (ev) => {
52
+ this.isDragging = false;
53
+ (0, mithril_1.redraw)();
54
+ ev.preventDefault();
55
+ };
56
+ document.addEventListener("click", onclick);
57
+ this.subs.add(() => document.removeEventListener("click", onclick));
58
+ document.addEventListener("dragstart", ondragstart);
59
+ this.subs.add(() => document.removeEventListener("dragstart", ondragstart));
60
+ document.addEventListener("dragend", ondragend);
61
+ this.subs.add(() => document.removeEventListener("dragend", ondragend));
62
+ document.addEventListener("dragenter", ondragenter);
63
+ this.subs.add(() => document.removeEventListener("dragenter", ondragenter));
64
+ document.addEventListener("dragover", ondragover);
65
+ this.subs.add(() => document.removeEventListener("dragover", ondragover));
66
+ document.addEventListener("dragleave", ondragleave);
67
+ this.subs.add(() => document.removeEventListener("dragleave", ondragleave));
68
+ document.addEventListener("drag", ondrag);
69
+ this.subs.add(() => document.removeEventListener("drag", ondrag));
70
+ document.addEventListener("drop", ondrop);
71
+ this.subs.add(() => document.removeEventListener("drop", ondrop));
72
+ }
73
+ destroy() {
74
+ this.subs.unsubscribe();
75
+ }
76
+ }
77
+ exports.DropTargetState = DropTargetState;
@@ -0,0 +1 @@
1
+ export * from "./DropTarget";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./DropTarget"), exports);
@@ -1,5 +1,7 @@
1
1
  export * from "./dialog";
2
+ export * from "./drop-target";
2
3
  export * from "./icon";
3
4
  export * from "./markdown";
5
+ export * from "./markdown-editor";
4
6
  export * from "./menu";
5
7
  export * from "./table";
@@ -19,9 +19,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
19
19
  // export * from "./data-table";
20
20
  __exportStar(require("./dialog"), exports);
21
21
  // export * from "./drawer";
22
+ __exportStar(require("./drop-target"), exports);
22
23
  // export * from "./form";
23
24
  __exportStar(require("./icon"), exports);
24
25
  __exportStar(require("./markdown"), exports);
26
+ __exportStar(require("./markdown-editor"), exports);
25
27
  __exportStar(require("./menu"), exports);
26
28
  // export * from "./modal";
27
29
  // export * from "./pagination";
@@ -0,0 +1,19 @@
1
+ import m from "mithril";
2
+ import { MarkdownEditorState } from "./markdown-editor.state";
3
+ export declare const MarkdownEditor: m.Component<{
4
+ /** The current markdown content as a string */
5
+ markdown: string;
6
+ /** Function called whenever the markdown string is edited */
7
+ onEdit: (markdown: string) => any;
8
+ /**
9
+ * Attempt to highlight syntax of code blocks
10
+ * @default false
11
+ */
12
+ highlight?: boolean;
13
+ /** @default true */
14
+ enableShortcuts?: boolean;
15
+ class?: string;
16
+ style?: string;
17
+ }, {
18
+ state: MarkdownEditorState;
19
+ }>;
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.MarkdownEditor = void 0;
27
+ const mithril_1 = __importStar(require("mithril"));
28
+ const Icon_1 = require("../icon/Icon");
29
+ const Markdown_1 = require("../markdown/Markdown");
30
+ const menu_1 = require("../menu");
31
+ const markdown_editor_state_1 = require("./markdown-editor.state");
32
+ exports.MarkdownEditor = {
33
+ oninit({ attrs }) {
34
+ this.state = new markdown_editor_state_1.MarkdownEditorState(attrs.onEdit);
35
+ },
36
+ onremove() {
37
+ this.state.destroy();
38
+ },
39
+ onupdate({ attrs, dom }) {
40
+ if (this.state.isEditing && !this.state.editorContainerEl) {
41
+ this.state.editorContainerEl = [...dom.getElementsByClassName("editor-container")].at(0);
42
+ }
43
+ if (this.state.isEditing && !this.state.editorEl) {
44
+ this.state.editorEl = [...dom.getElementsByTagName("textarea")].at(0);
45
+ }
46
+ if (this.state.isEditing && !this.state.isEditingPrevious) {
47
+ const editorEl = [...dom.getElementsByTagName("textarea")].at(0);
48
+ editorEl === null || editorEl === void 0 ? void 0 : editorEl.focus();
49
+ editorEl === null || editorEl === void 0 ? void 0 : editorEl.setSelectionRange(0, 0);
50
+ if (editorEl)
51
+ this.state.editorEl = editorEl;
52
+ this.state.editorValue = attrs.markdown;
53
+ this.state.editValue = attrs.onEdit;
54
+ this.state.setSelection(0, 0);
55
+ setTimeout(() => {
56
+ (0, mithril_1.redraw)();
57
+ }, 1);
58
+ }
59
+ if (this.state.requestSelection && this.state.editorEl) {
60
+ this.state.editorEl.setSelectionRange(this.state.requestSelection.start, this.state.requestSelection.end);
61
+ this.state.requestSelection = undefined;
62
+ (0, mithril_1.redraw)();
63
+ }
64
+ this.state.isEditingPrevious = this.state.isEditing;
65
+ },
66
+ view({ attrs }) {
67
+ return (0, mithril_1.default)(".mico-markdown-editor", {
68
+ class: attrs.class,
69
+ style: attrs.style,
70
+ }, [
71
+ (!this.state.isEditing) ? [
72
+ (0, mithril_1.default)(".render-container", {
73
+ tabindex: 0,
74
+ onclick: () => {
75
+ this.state.isEditing = true;
76
+ },
77
+ }, [
78
+ (0, mithril_1.default)(Markdown_1.Markdown, {
79
+ highlight: attrs.highlight,
80
+ markdown: attrs.markdown,
81
+ }),
82
+ ]),
83
+ ] : [
84
+ (0, mithril_1.default)(".editor-container", [
85
+ // Editor buttons
86
+ (0, mithril_1.default)(".editor-buttons", [
87
+ (0, mithril_1.default)(".editor-button-group", [
88
+ (0, mithril_1.default)("button", {
89
+ title: "Ctrl+Shift+H",
90
+ onclick: () => this.state.applyHeading(),
91
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "heading" })),
92
+ (0, mithril_1.default)("button", {
93
+ onclick: (ev) => menu_1.menuMgr.show(ev, [
94
+ (0, mithril_1.default)(menu_1.MenuButton, { label: "Heading 1", onclick: () => this.state.applyHeading(1) }),
95
+ (0, mithril_1.default)(menu_1.MenuButton, { label: "Heading 2", onclick: () => this.state.applyHeading(2) }),
96
+ (0, mithril_1.default)(menu_1.MenuButton, { label: "Heading 3", onclick: () => this.state.applyHeading(3) }),
97
+ (0, mithril_1.default)(menu_1.MenuButton, { label: "Heading 4", onclick: () => this.state.applyHeading(4) }),
98
+ ], {
99
+ noFlipH: true,
100
+ noFlipV: true,
101
+ }),
102
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "caret-down" })),
103
+ ]),
104
+ (0, mithril_1.default)(".editor-button-group", [
105
+ (0, mithril_1.default)("button", {
106
+ title: "Ctrl+Shift+B",
107
+ onclick: () => this.state.applyBold(),
108
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "bold" })),
109
+ (0, mithril_1.default)("button", {
110
+ title: "Ctrl+Shift+I",
111
+ onclick: () => this.state.applyItalic(),
112
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "italic" })),
113
+ (0, mithril_1.default)("button", {
114
+ title: "Ctrl+Shift+C",
115
+ onclick: () => this.state.applyCode(),
116
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "code" })),
117
+ (0, mithril_1.default)("button", {
118
+ title: "Ctrl+Shift+S",
119
+ onclick: () => this.state.applyStrikethrough(),
120
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "strikethrough" })),
121
+ ]),
122
+ (0, mithril_1.default)(".editor-button-group", [
123
+ (0, mithril_1.default)("button", {
124
+ title: "Ctrl+Shift+U",
125
+ onclick: () => this.state.applyUnorderedList(),
126
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "list-ul" })),
127
+ (0, mithril_1.default)("button", {
128
+ title: "Ctrl+Shift+O",
129
+ onclick: () => this.state.applyOrderedList(),
130
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "list-ol" })),
131
+ (0, mithril_1.default)("button", {
132
+ title: "Ctrl+Shift+L",
133
+ onclick: () => this.state.applyLink(),
134
+ }, (0, mithril_1.default)(Icon_1.Icon, { fas: "link" })),
135
+ ]),
136
+ ]),
137
+ // Editor content
138
+ (0, mithril_1.default)("textarea.editor-textarea", {
139
+ rows: this.state.editorValue.split("\n").length + 2,
140
+ value: this.state.editorValue,
141
+ oninput: (e) => {
142
+ this.state.editorValue = e.target.value;
143
+ this.state.editValue(e.target.value);
144
+ },
145
+ onmousemove: (ev) => {
146
+ const textareaEl = ev.currentTarget;
147
+ this.state.setSelection(textareaEl.selectionStart, textareaEl.selectionEnd);
148
+ },
149
+ onkeyup: (ev) => {
150
+ const textareaEl = ev.currentTarget;
151
+ this.state.setSelection(textareaEl.selectionStart, textareaEl.selectionEnd);
152
+ },
153
+ onkeydown: (ev) => {
154
+ const textareaEl = ev.currentTarget;
155
+ this.state.setSelection(textareaEl.selectionStart, textareaEl.selectionEnd);
156
+ if (ev.code === "Escape")
157
+ this.state.isEditing = false;
158
+ if (ev.ctrlKey && ev.shiftKey) {
159
+ if (ev.key.toLowerCase() === "h") {
160
+ this.state.applyHeading();
161
+ ev.preventDefault();
162
+ }
163
+ if (ev.key.toLowerCase() === "b") {
164
+ this.state.applyBold();
165
+ ev.preventDefault();
166
+ }
167
+ if (ev.key.toLowerCase() === "i") {
168
+ this.state.applyItalic();
169
+ ev.preventDefault();
170
+ }
171
+ if (ev.key.toLowerCase() === "c") {
172
+ this.state.applyCode();
173
+ ev.preventDefault();
174
+ }
175
+ if (ev.key.toLowerCase() === "s") {
176
+ this.state.applyStrikethrough();
177
+ ev.preventDefault();
178
+ }
179
+ if (ev.key.toLowerCase() === "u") {
180
+ this.state.applyUnorderedList();
181
+ ev.preventDefault();
182
+ }
183
+ if (ev.key.toLowerCase() === "o") {
184
+ this.state.applyOrderedList();
185
+ ev.preventDefault();
186
+ }
187
+ if (ev.key.toLowerCase() === "l") {
188
+ this.state.applyLink();
189
+ ev.preventDefault();
190
+ }
191
+ }
192
+ if (ev.key === "Tab") {
193
+ this.state.applyIndent(ev.shiftKey);
194
+ ev.preventDefault();
195
+ }
196
+ },
197
+ }),
198
+ ]),
199
+ ],
200
+ ]);
201
+ }
202
+ };
@@ -0,0 +1 @@
1
+ export * from "./MarkdownEditor";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./MarkdownEditor"), exports);
@@ -0,0 +1,31 @@
1
+ import { Subscription } from "rxjs";
2
+ export declare class MarkdownEditorState {
3
+ subs: Subscription;
4
+ isEditing: boolean;
5
+ isEditingPrevious: boolean;
6
+ editorValue: string;
7
+ editorEl: HTMLTextAreaElement | undefined;
8
+ editorContainerEl: HTMLDivElement | undefined;
9
+ editValue: (markdown: string) => any;
10
+ selectionStart: number;
11
+ selectionEnd: number;
12
+ requestSelection: {
13
+ start: number;
14
+ end: number;
15
+ } | undefined;
16
+ constructor(editValue: (markdown: string) => any);
17
+ private selectedText;
18
+ private findSelectedLines;
19
+ private findSelectedWord;
20
+ destroy(): void;
21
+ setSelection(start: number, end: number): void;
22
+ applyHeading(level?: number): void;
23
+ applyBold(): void;
24
+ applyItalic(): void;
25
+ applyCode(): void;
26
+ applyStrikethrough(): void;
27
+ applyUnorderedList(): void;
28
+ applyOrderedList(): void;
29
+ applyLink(): void;
30
+ applyIndent(backward?: boolean): void;
31
+ }
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MarkdownEditorState = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ const utils_1 = require("../../utils");
6
+ const mithril_1 = require("mithril");
7
+ class MarkdownEditorState {
8
+ constructor(editValue) {
9
+ this.subs = new rxjs_1.Subscription();
10
+ this.isEditing = false;
11
+ this.isEditingPrevious = false;
12
+ this.editorValue = "";
13
+ this.editorEl = undefined;
14
+ this.editorContainerEl = undefined;
15
+ this.selectionStart = 0;
16
+ this.selectionEnd = 0;
17
+ this.editValue = editValue;
18
+ const onclick = (ev) => {
19
+ if (this.isEditing && this.editorContainerEl) {
20
+ if (!(0, utils_1.mouseWithinElement)(ev, this.editorContainerEl)) {
21
+ this.isEditing = false;
22
+ (0, mithril_1.redraw)();
23
+ }
24
+ }
25
+ };
26
+ document.addEventListener("click", onclick);
27
+ this.subs.add(() => document.removeEventListener("click", onclick));
28
+ }
29
+ selectedText() {
30
+ const selection = this.editorValue.slice(this.selectionStart, this.selectionEnd);
31
+ return selection;
32
+ }
33
+ findSelectedLines() {
34
+ // if (!this.editorEl) return [];
35
+ let linesStart = this.selectionStart;
36
+ let linesEnd = this.selectionEnd;
37
+ for (let i = this.selectionStart; i >= 0; i--) {
38
+ if (i > 1 && this.editorValue[i - 1] === "\n") {
39
+ linesStart = i;
40
+ break;
41
+ }
42
+ if (i === 0) {
43
+ linesStart = i;
44
+ break;
45
+ }
46
+ }
47
+ for (let i = this.selectionEnd; i < this.editorValue.length; i++) {
48
+ if (this.editorValue[i] === "\n" || i === 0) {
49
+ linesEnd = i;
50
+ break;
51
+ }
52
+ }
53
+ const selectedLinesString = this.editorValue.slice(linesStart, linesEnd);
54
+ // console.log("selectedLinesStr", linesStart, linesEnd, selectedLinesString);
55
+ let selectedLinesText = selectedLinesString.split("\n");
56
+ selectedLinesText = selectedLinesText.map((line, lineIndex) => {
57
+ return line + ((lineIndex <= (selectedLinesText.length - 2)) ? "\n" : "");
58
+ });
59
+ const lines = [];
60
+ let offset = linesStart;
61
+ for (let i = 0; i < selectedLinesText.length; i++) {
62
+ lines.push({
63
+ start: offset,
64
+ end: offset + selectedLinesText[i].length,
65
+ content: selectedLinesText[i],
66
+ });
67
+ offset = offset + selectedLinesText[i].length;
68
+ }
69
+ // console.log("lines", lines);
70
+ return lines;
71
+ }
72
+ findSelectedWord() {
73
+ let start = this.selectionStart;
74
+ let end = this.selectionEnd;
75
+ for (let i = this.selectionStart; i >= 0; i--) {
76
+ if (i > 1 && (this.editorValue[i - 1] === " " || this.editorValue[i - 1] === "\n")) {
77
+ start = i;
78
+ break;
79
+ }
80
+ if (i === 0) {
81
+ start = i;
82
+ break;
83
+ }
84
+ }
85
+ for (let i = this.selectionEnd; i < this.editorValue.length; i++) {
86
+ if (this.editorValue[i] === "" || this.editorValue[i] === "\n" || i === 0) {
87
+ end = i;
88
+ break;
89
+ }
90
+ }
91
+ return {
92
+ start,
93
+ end,
94
+ content: this.editorValue.slice(start, end),
95
+ };
96
+ }
97
+ destroy() {
98
+ this.subs.unsubscribe();
99
+ }
100
+ setSelection(start, end) {
101
+ this.selectionStart = start;
102
+ this.selectionEnd = end;
103
+ const selection = this.editorValue.slice(start, end);
104
+ // console.log(start, end, selection);
105
+ }
106
+ applyHeading(level = 1) {
107
+ // console.log("addHeading", "selection:", this.selectionStart, this.selectionEnd);
108
+ let value = this.editorValue;
109
+ const toInsert = "#".padEnd(level, "#") + " ";
110
+ const lines = this.findSelectedLines();
111
+ for (let i = lines.length - 1; i >= 0; i--) {
112
+ value = value.slice(0, lines[i].start) + toInsert + value.slice(lines[i].start);
113
+ }
114
+ this.editorValue = value;
115
+ this.editValue(value);
116
+ const newSelection = this.selectionStart + toInsert.length;
117
+ this.requestSelection = { start: newSelection, end: newSelection };
118
+ }
119
+ applyBold() {
120
+ // console.log("addBold", "selection:", this.selectionStart, this.selectionEnd);
121
+ let start = this.selectionStart;
122
+ let end = this.selectionEnd;
123
+ if (start === end) {
124
+ const word = this.findSelectedWord();
125
+ start = word.start;
126
+ end = word.end;
127
+ }
128
+ let value = this.editorValue;
129
+ value = value.slice(0, end) + "**" + value.slice(end);
130
+ value = value.slice(0, start) + "**" + value.slice(start);
131
+ this.editorValue = value;
132
+ this.editValue(value);
133
+ const newSelection = start + 2;
134
+ this.requestSelection = { start: newSelection, end: newSelection };
135
+ }
136
+ applyItalic() {
137
+ // console.log("addItalic", "selection:", this.selectionStart, this.selectionEnd);
138
+ let start = this.selectionStart;
139
+ let end = this.selectionEnd;
140
+ if (start === end) {
141
+ const word = this.findSelectedWord();
142
+ start = word.start;
143
+ end = word.end;
144
+ }
145
+ let value = this.editorValue;
146
+ value = value.slice(0, end) + "*" + value.slice(end);
147
+ value = value.slice(0, start) + "*" + value.slice(start);
148
+ this.editorValue = value;
149
+ this.editValue(value);
150
+ const newSelection = start + 1;
151
+ this.requestSelection = { start: newSelection, end: newSelection };
152
+ }
153
+ applyCode() {
154
+ // console.log("addCode", "selection:", this.selectionStart, this.selectionEnd);
155
+ let start = this.selectionStart;
156
+ let end = this.selectionEnd;
157
+ if (start === end) {
158
+ const word = this.findSelectedWord();
159
+ start = word.start;
160
+ end = word.end;
161
+ }
162
+ let value = this.editorValue;
163
+ value = value.slice(0, end) + "`" + value.slice(end);
164
+ value = value.slice(0, start) + "`" + value.slice(start);
165
+ this.editorValue = value;
166
+ this.editValue(value);
167
+ const newSelection = start + 1;
168
+ this.requestSelection = { start: newSelection, end: newSelection };
169
+ }
170
+ applyStrikethrough() {
171
+ // console.log("addStrikethrough", "selection:", this.selectionStart, this.selectionEnd);
172
+ let start = this.selectionStart;
173
+ let end = this.selectionEnd;
174
+ if (start === end) {
175
+ const word = this.findSelectedWord();
176
+ start = word.start;
177
+ end = word.end;
178
+ }
179
+ let value = this.editorValue;
180
+ value = value.slice(0, end) + "~~" + value.slice(end);
181
+ value = value.slice(0, start) + "~~" + value.slice(start);
182
+ this.editorValue = value;
183
+ this.editValue(value);
184
+ const newSelection = start + 2;
185
+ this.requestSelection = { start: newSelection, end: newSelection };
186
+ }
187
+ applyUnorderedList() {
188
+ // console.log("addUnorderedList", "selection:", this.selectionStart, this.selectionEnd);
189
+ let value = this.editorValue;
190
+ const toInsert = "- ";
191
+ const lines = this.findSelectedLines();
192
+ for (let i = lines.length - 1; i >= 0; i--) {
193
+ value = value.slice(0, lines[i].start) + toInsert + value.slice(lines[i].start);
194
+ }
195
+ this.editorValue = value;
196
+ this.editValue(value);
197
+ const newSelection = this.selectionStart + 2;
198
+ this.requestSelection = { start: newSelection, end: newSelection };
199
+ }
200
+ applyOrderedList() {
201
+ // console.log("addOrderedList", "selection:", this.selectionStart, this.selectionEnd);
202
+ let value = this.editorValue;
203
+ const lines = this.findSelectedLines();
204
+ for (let i = lines.length - 1; i >= 0; i--) {
205
+ let toInsert = `${i + 1}. `;
206
+ value = value.slice(0, lines[i].start) + toInsert + value.slice(lines[i].start);
207
+ }
208
+ this.editorValue = value;
209
+ this.editValue(value);
210
+ const newSelection = this.selectionStart + 3;
211
+ this.requestSelection = { start: newSelection, end: newSelection };
212
+ }
213
+ applyLink() {
214
+ // console.log("addLink", "selection:", this.selectionStart, this.selectionEnd);
215
+ let start = this.selectionStart;
216
+ let end = this.selectionEnd;
217
+ let content = this.selectedText();
218
+ if (start === end) {
219
+ const word = this.findSelectedWord();
220
+ start = word.start;
221
+ end = word.end;
222
+ content = word.content;
223
+ }
224
+ const isUrl = content.startsWith("http") || content.startsWith("www");
225
+ let url = (Boolean(content) && isUrl) ? content : "https://example.com";
226
+ let value = this.editorValue;
227
+ value = value.slice(0, end) + `](${url})` + value.slice(end);
228
+ value = value.slice(0, start) + "[" + value.slice(start);
229
+ this.editorValue = value;
230
+ this.editValue(value);
231
+ const newSelection = start + 1;
232
+ this.requestSelection = { start: newSelection, end: newSelection };
233
+ }
234
+ applyIndent(backward = false) {
235
+ // console.log("applyIndent", "selection:", this.selectionStart, this.selectionEnd);
236
+ let value = this.editorValue;
237
+ // const toInsert = "- ";
238
+ const lines = this.findSelectedLines();
239
+ for (let i = lines.length - 1; i >= 0; i--) {
240
+ if (backward) {
241
+ let newContent = lines[i].content;
242
+ if (newContent.startsWith(" "))
243
+ newContent = newContent.slice(1);
244
+ if (newContent.startsWith(" "))
245
+ newContent = newContent.slice(1);
246
+ value = value.slice(0, lines[i].start) + newContent + value.slice(lines[i].end);
247
+ }
248
+ else {
249
+ value = value.slice(0, lines[i].start) + " " + value.slice(lines[i].start);
250
+ }
251
+ }
252
+ this.editorValue = value;
253
+ this.editValue(value);
254
+ let newSelection = this.selectionStart + (backward ? -2 : 2);
255
+ newSelection = Math.max(newSelection, lines[0].start);
256
+ this.requestSelection = { start: newSelection, end: newSelection };
257
+ }
258
+ }
259
+ exports.MarkdownEditorState = MarkdownEditorState;
@@ -1,7 +1,14 @@
1
1
  import m from "mithril";
2
+ export interface MenuOptions {
3
+ noFlipV?: boolean;
4
+ noFlipH?: boolean;
5
+ forceFlipV?: boolean;
6
+ forceFlipH?: boolean;
7
+ }
2
8
  export declare const Menu: m.Component<{
3
9
  x: number | undefined;
4
10
  y: number | undefined;
11
+ options?: MenuOptions;
5
12
  }, {
6
13
  el?: HTMLDivElement;
7
14
  }>;
@@ -18,16 +18,24 @@ exports.Menu = {
18
18
  onupdate({ dom }) {
19
19
  // if (dom) this.el = dom as HTMLDivElement;
20
20
  },
21
- view({ attrs, children, }) {
22
- var _a, _b;
21
+ view({ attrs, children }) {
22
+ var _a, _b, _c, _d, _e, _f;
23
23
  const maxWidthPx = Math.min(window.innerWidth, menu_constants_1.MENU_CONSTANTS.MAX_WIDTH_PX);
24
24
  const maxHeightPx = window.innerHeight / 2;
25
25
  const xPx = (_a = attrs.x) !== null && _a !== void 0 ? _a : 0;
26
26
  const yPx = (_b = attrs.y) !== null && _b !== void 0 ? _b : 0;
27
- const flipH = ((xPx + menu_constants_1.MENU_CONSTANTS.MIN_WIDTH_PX) > window.innerWidth);
28
- const flipV = menu_settings_1.MENU_SETTINGS.ANCHOR === "page"
27
+ let flipH = ((xPx + menu_constants_1.MENU_CONSTANTS.MIN_WIDTH_PX) > window.innerWidth);
28
+ let flipV = menu_settings_1.MENU_SETTINGS.ANCHOR === "page"
29
29
  ? ((yPx + maxHeightPx - window.scrollY) > window.innerHeight)
30
30
  : ((yPx + maxHeightPx) > window.innerHeight);
31
+ if (((_c = attrs.options) === null || _c === void 0 ? void 0 : _c.noFlipH) === true)
32
+ flipH = false;
33
+ if (((_d = attrs.options) === null || _d === void 0 ? void 0 : _d.noFlipV) === true)
34
+ flipV = false;
35
+ if (((_e = attrs.options) === null || _e === void 0 ? void 0 : _e.forceFlipH) === true)
36
+ flipH = true;
37
+ if (((_f = attrs.options) === null || _f === void 0 ? void 0 : _f.forceFlipV) === true)
38
+ flipV = true;
31
39
  // console.log({
32
40
  // "yPx": yPx,
33
41
  // "maxHeightPx": maxHeightPx,
@@ -1,4 +1,5 @@
1
1
  import m from "mithril";
2
+ import { MenuOptions } from "./Menu";
2
3
  export declare class MenuManager {
3
4
  private containerEl;
4
5
  private _menuVnode;
@@ -15,7 +16,7 @@ export declare class MenuManager {
15
16
  * @param ev
16
17
  * @param menuContents
17
18
  */
18
- show(ev: MouseEvent | undefined, menuContents: m.ChildArrayOrPrimitive): Promise<void>;
19
+ show(ev: MouseEvent | undefined, menuContents: m.ChildArrayOrPrimitive, options?: MenuOptions): Promise<void>;
19
20
  hide(): void;
20
21
  }
21
22
  export declare const menuMgr: MenuManager;
@@ -48,7 +48,7 @@ class MenuManager {
48
48
  * @param ev
49
49
  * @param menuContents
50
50
  */
51
- show(ev, menuContents) {
51
+ show(ev, menuContents, options) {
52
52
  return __awaiter(this, void 0, void 0, function* () {
53
53
  ev === null || ev === void 0 ? void 0 : ev.stopPropagation();
54
54
  if (this.isOpen) {
@@ -60,6 +60,7 @@ class MenuManager {
60
60
  const menuVnode = (0, mithril_1.default)(Menu_1.Menu, {
61
61
  x: ev === null || ev === void 0 ? void 0 : ev.clientX,
62
62
  y: menu_settings_1.MENU_SETTINGS.ANCHOR === "page" ? ev === null || ev === void 0 ? void 0 : ev.pageY : ev === null || ev === void 0 ? void 0 : ev.clientY,
63
+ options,
63
64
  }, [
64
65
  menuContents,
65
66
  ]);
@@ -0,0 +1,8 @@
1
+ export declare function dragEventFiles(ev: DragEvent): File[];
2
+ /**
3
+ * Find whether a mouse event is within a given DOM element using clientX/Y and the bounding client rect
4
+ * @param ev
5
+ * @param element
6
+ * @returns
7
+ */
8
+ export declare function mouseWithinElement(ev: MouseEvent, element: Element): boolean;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mouseWithinElement = exports.dragEventFiles = void 0;
4
+ function dragEventFiles(ev) {
5
+ var _a, _b, _c;
6
+ const files = [];
7
+ if ((_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.items) {
8
+ // Use DataTransferItemList interface to access the file(s)
9
+ [...ev.dataTransfer.items].forEach((item, i) => {
10
+ // If dropped items aren't files, reject them
11
+ if (item.kind === "file") {
12
+ const file = item.getAsFile();
13
+ if (file) {
14
+ // console.log(`… file[${i}].name = ${file.name}`);
15
+ files.push(file);
16
+ }
17
+ }
18
+ });
19
+ }
20
+ else {
21
+ // Use DataTransfer interface to access the file(s)
22
+ [...(_c = (_b = ev.dataTransfer) === null || _b === void 0 ? void 0 : _b.files) !== null && _c !== void 0 ? _c : []].forEach((file, i) => {
23
+ // console.log(`… file[${i}].name = ${file.name}`);
24
+ files.push(file);
25
+ });
26
+ }
27
+ return files;
28
+ }
29
+ exports.dragEventFiles = dragEventFiles;
30
+ /**
31
+ * Find whether a mouse event is within a given DOM element using clientX/Y and the bounding client rect
32
+ * @param ev
33
+ * @param element
34
+ * @returns
35
+ */
36
+ function mouseWithinElement(ev, element) {
37
+ const elementRect = element.getBoundingClientRect();
38
+ const isWithin = (ev.clientY >= elementRect.top) &&
39
+ (ev.clientY <= elementRect.bottom) &&
40
+ (ev.clientX >= elementRect.left) &&
41
+ (ev.clientX <= elementRect.right);
42
+ return isWithin;
43
+ }
44
+ exports.mouseWithinElement = mouseWithinElement;
@@ -1 +1,2 @@
1
1
  export * from "./date-time";
2
+ export * from "./events";
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./date-time"), exports);
18
+ __exportStar(require("./events"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypericon/mico",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "UI components and utilities with Mithril",
5
5
  "author": "Ed Johnson, Hypericon Ltd",
6
6
  "license": "MIT",