@chelone/core 0.4.0 → 0.6.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 (38) hide show
  1. package/dist/assistant/assistant-session.d.ts +22 -0
  2. package/dist/assistant/assistant-session.js +185 -0
  3. package/dist/assistant/echo-transport.d.ts +7 -0
  4. package/dist/assistant/echo-transport.js +31 -0
  5. package/dist/assistant/types.d.ts +60 -0
  6. package/dist/chart/chart-data.d.ts +25 -0
  7. package/dist/chart/chart-data.js +59 -0
  8. package/dist/controller/infinite-list-controller.js +1 -1
  9. package/dist/controller/job-monitor.d.ts +42 -0
  10. package/dist/controller/job-monitor.js +59 -0
  11. package/dist/controller/pivot-grid.d.ts +36 -0
  12. package/dist/controller/pivot-grid.js +105 -0
  13. package/dist/controller/record-timeline.d.ts +35 -0
  14. package/dist/controller/record-timeline.js +36 -0
  15. package/dist/data/cache-ops.js +1 -3
  16. package/dist/form/edit-create-form.js +7 -7
  17. package/dist/i18n/i18n.d.ts +6 -0
  18. package/dist/i18n/i18n.js +13 -1
  19. package/dist/i18n/missing-keys.d.ts +19 -0
  20. package/dist/i18n/missing-keys.js +64 -0
  21. package/dist/index.d.ts +27 -0
  22. package/dist/index.js +43 -0
  23. package/dist/onboarding-tour/placement.d.ts +34 -0
  24. package/dist/onboarding-tour/placement.js +82 -0
  25. package/dist/onboarding-tour/tour-controller.d.ts +25 -0
  26. package/dist/onboarding-tour/tour-controller.js +58 -0
  27. package/dist/realtime/announce-lock.d.ts +7 -0
  28. package/dist/realtime/announce-lock.js +15 -0
  29. package/dist/realtime/broadcast-channel-transport.d.ts +14 -0
  30. package/dist/realtime/broadcast-channel-transport.js +63 -0
  31. package/dist/realtime/in-memory-lock-provider.js +3 -14
  32. package/dist/realtime/memory-transport.js +10 -51
  33. package/dist/realtime/reconnect-backoff.d.ts +15 -0
  34. package/dist/realtime/topic-hub.d.ts +24 -0
  35. package/dist/realtime/topic-hub.js +75 -0
  36. package/dist/rich-text/rich-text.d.ts +56 -0
  37. package/dist/rich-text/rich-text.js +105 -0
  38. package/package.json +3 -3
@@ -0,0 +1,58 @@
1
+ import { createMicroStore } from "../store/micro-store.js";
2
+ function onboardingTourKey(id) {
3
+ return `onboarding-tour.${id}.completed`;
4
+ }
5
+ function createTourController({
6
+ id,
7
+ stepCount,
8
+ store,
9
+ autoStart = true,
10
+ onComplete
11
+ }) {
12
+ const key = onboardingTourKey(id);
13
+ const isDone = () => store.getItem(key) === true;
14
+ const micro = createMicroStore(
15
+ { active: false, index: 0 },
16
+ {
17
+ // The store is only watched while someone is subscribed, so an unmounted
18
+ // tour does not reopen when its flag is cleared.
19
+ onSubscribed() {
20
+ const openIfDue = () => {
21
+ if (autoStart && !isDone() && !micro.getSnapshot().active) start();
22
+ };
23
+ openIfDue();
24
+ return store.subscribe(key, openIfDue);
25
+ }
26
+ }
27
+ );
28
+ function start() {
29
+ if (stepCount < 1) return;
30
+ micro.setState({ active: true, index: 0 });
31
+ }
32
+ function end(reason) {
33
+ if (!micro.getSnapshot().active) return;
34
+ micro.setState({ active: false });
35
+ store.setItem(key, true);
36
+ onComplete?.(reason);
37
+ }
38
+ return {
39
+ ...micro,
40
+ start,
41
+ next() {
42
+ const { active, index } = micro.getSnapshot();
43
+ if (!active) return;
44
+ if (index >= stepCount - 1) end("finished");
45
+ else micro.setState({ index: index + 1 });
46
+ },
47
+ back() {
48
+ const { active, index } = micro.getSnapshot();
49
+ if (active && index > 0) micro.setState({ index: index - 1 });
50
+ },
51
+ skip: () => end("skipped"),
52
+ finish: () => end("finished")
53
+ };
54
+ }
55
+ export {
56
+ createTourController,
57
+ onboardingTourKey
58
+ };
@@ -0,0 +1,7 @@
1
+ import type { Lock, RealtimeTransport } from "./types.js";
2
+ /**
3
+ * Tells a publisher of a lock or an unlock, on the record's and the
4
+ * resource's topic. A failed publish is swallowed: the lock is already taken
5
+ * or released, so a failed announcement must not report it as failed.
6
+ */
7
+ export declare function announceLock(publisher: Pick<RealtimeTransport, "publish"> | undefined, type: "locked" | "unlocked", lock: Lock): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { lockTopic, lockResourceTopic } from "./topics.js";
2
+ async function announceLock(publisher, type, lock) {
3
+ if (!publisher) return;
4
+ const event = { type, payload: lock };
5
+ try {
6
+ await Promise.all([
7
+ publisher.publish(lockTopic(lock.resource, lock.recordId), event),
8
+ publisher.publish(lockResourceTopic(lock.resource), event)
9
+ ]);
10
+ } catch {
11
+ }
12
+ }
13
+ export {
14
+ announceLock
15
+ };
@@ -0,0 +1,14 @@
1
+ import type { RealtimeTransport, RealtimeTransportError } from "./types.js";
2
+ export interface BroadcastChannelTransportOptions {
3
+ /** The channel name; every tab that uses the same name shares events. */
4
+ channel: string;
5
+ /** Called for every transport error, like a listener from `onError()`. */
6
+ onError?: (error: RealtimeTransportError) => void;
7
+ }
8
+ /** A `RealtimeTransport` with every optional member, plus `close()`. */
9
+ export interface BroadcastChannelTransport extends Required<RealtimeTransport> {
10
+ /** Stops listening, closes the channel and sets `"disconnected"`. */
11
+ close(): void;
12
+ }
13
+ /** Carries realtime events between the tabs of one browser, with no server. */
14
+ export declare function broadcastChannelTransport({ channel, onError, }: BroadcastChannelTransportOptions): BroadcastChannelTransport;
@@ -0,0 +1,63 @@
1
+ import { createTopicHub } from "./topic-hub.js";
2
+ function isEnvelope(value) {
3
+ if (typeof value !== "object" || value === null) return false;
4
+ if (!("topic" in value) || typeof value.topic !== "string") return false;
5
+ if (!("event" in value)) return false;
6
+ const { event } = value;
7
+ return typeof event === "object" && event !== null && "type" in event && typeof event.type === "string";
8
+ }
9
+ function broadcastChannelTransport({
10
+ channel,
11
+ onError
12
+ }) {
13
+ if (typeof BroadcastChannel === "undefined") {
14
+ throw new Error(
15
+ "broadcastChannelTransport needs the global BroadcastChannel, which this environment does not have."
16
+ );
17
+ }
18
+ const hub = createTopicHub({ onError });
19
+ const broadcast = new BroadcastChannel(channel);
20
+ let closed = false;
21
+ const onMessage = (message) => {
22
+ if (!isEnvelope(message.data)) {
23
+ hub.report({
24
+ kind: "parse_failed",
25
+ cause: message.data,
26
+ retrying: false
27
+ });
28
+ return;
29
+ }
30
+ const { topic, event } = message.data;
31
+ hub.dispatch({ payload: void 0, ...event, topic });
32
+ };
33
+ broadcast.addEventListener("message", onMessage);
34
+ return {
35
+ subscribe: hub.subscribe,
36
+ async publish(topic, event) {
37
+ if (closed) {
38
+ throw new Error("broadcastChannelTransport is closed");
39
+ }
40
+ try {
41
+ broadcast.postMessage({ topic, event });
42
+ } catch (cause) {
43
+ hub.report({ kind: "send_failed", topic, cause, retrying: false });
44
+ throw cause;
45
+ }
46
+ hub.dispatch({ ...event, topic });
47
+ },
48
+ onReconnect: hub.onReconnect,
49
+ onStatusChange: hub.onStatusChange,
50
+ onError: hub.onError,
51
+ getStatus: hub.getStatus,
52
+ close() {
53
+ if (closed) return;
54
+ closed = true;
55
+ broadcast.removeEventListener("message", onMessage);
56
+ broadcast.close();
57
+ hub.setStatus("disconnected");
58
+ }
59
+ };
60
+ }
61
+ export {
62
+ broadcastChannelTransport
63
+ };
@@ -1,21 +1,10 @@
1
- import { lockTopic, lockResourceTopic } from "./topics.js";
1
+ import { announceLock } from "./announce-lock.js";
2
2
  import { LockConflictError } from "./types.js";
3
3
  const storeKey = (resource, id) => JSON.stringify([resource, String(id)]);
4
4
  function inMemoryLockProvider({
5
5
  publisher
6
6
  } = {}) {
7
7
  const store = /* @__PURE__ */ new Map();
8
- const announce = async (type, lock) => {
9
- if (!publisher) return;
10
- const event = { type, payload: lock };
11
- try {
12
- await Promise.all([
13
- publisher.publish(lockTopic(lock.resource, lock.recordId), event),
14
- publisher.publish(lockResourceTopic(lock.resource), event)
15
- ]);
16
- } catch {
17
- }
18
- };
19
8
  return {
20
9
  async lock(resource, { id, identity }) {
21
10
  const key = storeKey(resource, id);
@@ -33,7 +22,7 @@ function inMemoryLockProvider({
33
22
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
34
23
  };
35
24
  store.set(key, lock);
36
- await announce("locked", lock);
25
+ await announceLock(publisher, "locked", lock);
37
26
  return lock;
38
27
  },
39
28
  async unlock(resource, { id, identity }) {
@@ -46,7 +35,7 @@ function inMemoryLockProvider({
46
35
  throw new LockConflictError(existing);
47
36
  }
48
37
  store.delete(key);
49
- await announce("unlocked", existing);
38
+ await announceLock(publisher, "unlocked", existing);
50
39
  return existing;
51
40
  },
52
41
  async getLock(resource, { id }) {
@@ -1,66 +1,25 @@
1
- const addListener = (listeners, listener) => {
2
- listeners.add(listener);
3
- return () => {
4
- listeners.delete(listener);
5
- };
6
- };
1
+ import { createTopicHub } from "./topic-hub.js";
7
2
  function memoryTransport({
8
3
  delayMs,
9
4
  onError
10
5
  } = {}) {
11
- const subscribers = /* @__PURE__ */ new Map();
6
+ const hub = createTopicHub({ onError });
12
7
  const publishedEvents = [];
13
- const reconnectListeners = /* @__PURE__ */ new Set();
14
- const statusListeners = /* @__PURE__ */ new Set();
15
- const errorListeners = /* @__PURE__ */ new Set();
16
- let status = "connected";
17
- const report = (error) => {
18
- for (const handler of [...onError ? [onError] : [], ...errorListeners]) {
19
- try {
20
- handler(error);
21
- } catch {
22
- }
23
- }
24
- };
25
8
  return {
26
9
  publishedEvents,
27
- subscribe(topic, callback) {
28
- const subscription = {
29
- callback
30
- };
31
- const set = subscribers.get(topic) ?? /* @__PURE__ */ new Set();
32
- set.add(subscription);
33
- subscribers.set(topic, set);
34
- return () => {
35
- set.delete(subscription);
36
- };
37
- },
10
+ subscribe: hub.subscribe,
38
11
  async publish(topic, event) {
39
12
  const full = { topic, ...event };
40
13
  publishedEvents.push(full);
41
14
  if (delayMs) await new Promise((resolve) => setTimeout(resolve, delayMs));
42
- const live = subscribers.get(topic);
43
- for (const subscription of [...live ?? []]) {
44
- if (!live?.has(subscription)) continue;
45
- const { callback } = subscription;
46
- try {
47
- callback(full);
48
- } catch (cause) {
49
- report({ kind: "handler_threw", topic, cause, retrying: false });
50
- }
51
- }
52
- },
53
- onReconnect: (callback) => addListener(reconnectListeners, callback),
54
- onStatusChange: (callback) => addListener(statusListeners, callback),
55
- onError: (callback) => addListener(errorListeners, callback),
56
- getStatus: () => status,
57
- simulateReconnect() {
58
- for (const listener of [...reconnectListeners]) listener();
15
+ hub.dispatch(full);
59
16
  },
60
- setStatus(next) {
61
- status = next;
62
- for (const listener of [...statusListeners]) listener(next);
63
- }
17
+ onReconnect: hub.onReconnect,
18
+ onStatusChange: hub.onStatusChange,
19
+ onError: hub.onError,
20
+ getStatus: hub.getStatus,
21
+ simulateReconnect: hub.reconnected,
22
+ setStatus: hub.setStatus
64
23
  };
65
24
  }
66
25
  export {
@@ -0,0 +1,15 @@
1
+ export interface RealtimeReconnectConfig {
2
+ /** Whether a dropped connection is retried. Default `true`. */
3
+ enabled?: boolean;
4
+ /** The wait before the first retry. Default 1000. */
5
+ initialDelayMs?: number;
6
+ /** The wait never grows past this. Default 30000. */
7
+ maxDelayMs?: number;
8
+ /** Retries before giving up. Default `Infinity`. */
9
+ maxAttempts?: number;
10
+ /** Share of each wait that is randomised, up or down. Default 0.3. */
11
+ jitter?: number;
12
+ }
13
+ export declare function resolveReconnectConfig(config?: RealtimeReconnectConfig): Required<RealtimeReconnectConfig>;
14
+ /** The wait before retry number `attempt` (from 0): exponential, capped, then jittered. */
15
+ export declare function reconnectDelay(attempt: number, config: Required<RealtimeReconnectConfig>, random?: () => number): number;
@@ -0,0 +1,24 @@
1
+ import type { RealtimeConnectionStatus, RealtimeEvent, RealtimeTransport, RealtimeTransportError } from "./types.js";
2
+ export interface TopicHubOptions {
3
+ /** Called for every reported error, like a listener from `onError()`. */
4
+ onError?: (error: RealtimeTransportError) => void;
5
+ }
6
+ /**
7
+ * The subscriber bookkeeping every transport needs: per-topic subscriptions,
8
+ * isolated dispatch, and the status, reconnect and error listeners.
9
+ */
10
+ export interface TopicHub extends Required<Pick<RealtimeTransport, "subscribe" | "onReconnect" | "onStatusChange" | "getStatus" | "onError">> {
11
+ /** Calls the subscribers of `event.topic`; a throwing one is reported. */
12
+ dispatch(event: RealtimeEvent): void;
13
+ /** Whether any subscriber is listening on `topic`. */
14
+ has(topic: string): boolean;
15
+ /** Every topic with a subscriber, in the order each was first subscribed. */
16
+ topics(): string[];
17
+ /** Calls the `onError` option and every `onError` listener. */
18
+ report(error: RealtimeTransportError): void;
19
+ setStatus(status: RealtimeConnectionStatus): void;
20
+ /** Calls every `onReconnect` listener. */
21
+ reconnected(): void;
22
+ }
23
+ /** Starts `"connected"`; a transport that connects asynchronously sets its own status. */
24
+ export declare function createTopicHub({ onError }?: TopicHubOptions): TopicHub;
@@ -0,0 +1,75 @@
1
+ const addListener = (listeners, listener) => {
2
+ listeners.add(listener);
3
+ return () => {
4
+ listeners.delete(listener);
5
+ };
6
+ };
7
+ const isolate = (call) => {
8
+ try {
9
+ call();
10
+ } catch {
11
+ }
12
+ };
13
+ function createTopicHub({ onError } = {}) {
14
+ const subscribers = /* @__PURE__ */ new Map();
15
+ const reconnectListeners = /* @__PURE__ */ new Set();
16
+ const statusListeners = /* @__PURE__ */ new Set();
17
+ const errorListeners = /* @__PURE__ */ new Set();
18
+ let status = "connected";
19
+ const report = (error) => {
20
+ for (const listener of [...onError ? [onError] : [], ...errorListeners]) {
21
+ isolate(() => listener(error));
22
+ }
23
+ };
24
+ return {
25
+ subscribe(topic, callback) {
26
+ const subscription = {
27
+ // type-escape: the hub stores callbacks for any payload; dispatch passes the event on unchanged.
28
+ callback
29
+ };
30
+ const set = subscribers.get(topic) ?? /* @__PURE__ */ new Set();
31
+ set.add(subscription);
32
+ subscribers.set(topic, set);
33
+ return () => {
34
+ set.delete(subscription);
35
+ if (set.size === 0 && subscribers.get(topic) === set) {
36
+ subscribers.delete(topic);
37
+ }
38
+ };
39
+ },
40
+ dispatch(event) {
41
+ const live = subscribers.get(event.topic);
42
+ for (const subscription of [...live ?? []]) {
43
+ if (!live?.has(subscription)) continue;
44
+ try {
45
+ subscription.callback(event);
46
+ } catch (cause) {
47
+ report({
48
+ kind: "handler_threw",
49
+ topic: event.topic,
50
+ cause,
51
+ retrying: false
52
+ });
53
+ }
54
+ }
55
+ },
56
+ has: (topic) => subscribers.has(topic),
57
+ topics: () => [...subscribers.keys()],
58
+ report,
59
+ onReconnect: (callback) => addListener(reconnectListeners, callback),
60
+ onStatusChange: (callback) => addListener(statusListeners, callback),
61
+ onError: (callback) => addListener(errorListeners, callback),
62
+ getStatus: () => status,
63
+ setStatus(next) {
64
+ status = next;
65
+ for (const listener of [...statusListeners])
66
+ isolate(() => listener(next));
67
+ },
68
+ reconnected() {
69
+ for (const listener of [...reconnectListeners]) isolate(listener);
70
+ }
71
+ };
72
+ }
73
+ export {
74
+ createTopicHub
75
+ };
@@ -0,0 +1,56 @@
1
+ /** Whether a link `href` is safe to store and render in rich text. */
2
+ export declare function isSafeLinkUrl(url: string): boolean;
3
+ export interface IsSafeImageSrcOptions {
4
+ /** Allow base64 raster `data:image/*` sources. SVG is never allowed. */
5
+ allowDataImages?: boolean;
6
+ }
7
+ /** Whether an image `src` is safe to store and render in rich text. */
8
+ export declare function isSafeImageSrc(src: string, options?: IsSafeImageSrcOptions): boolean;
9
+ /** DOMPurify config for stored rich text HTML. Spread it into `sanitize`. */
10
+ export declare const richTextSanitizeOptions: Readonly<{
11
+ FORBID_TAGS: readonly ["style", "form", "input", "button", "textarea", "select", "iframe", "object", "embed"];
12
+ FORBID_ATTR: readonly ["style"];
13
+ }>;
14
+ /**
15
+ * Options for tiptap's `StarterKit.configure`. `isAllowedUri` ignores tiptap's
16
+ * second argument, so the scheme rule is ours alone.
17
+ */
18
+ export declare const richTextStarterKitOptions: Readonly<{
19
+ heading: Readonly<{
20
+ levels: readonly [1, 2, 3, 4, 5, 6];
21
+ }>;
22
+ link: Readonly<{
23
+ openOnClick: false;
24
+ autolink: true;
25
+ defaultProtocol: "https";
26
+ isAllowedUri: (url: string) => boolean;
27
+ }>;
28
+ }>;
29
+ export type RichTextToolbarItemId = "bold" | "italic" | "underline" | "strike" | "code" | "clearFormatting" | "link" | "bulletList" | "orderedList" | "blockquote" | "codeBlock" | "horizontalRule" | "undo" | "redo";
30
+ export type RichTextToolbarGroup = "mark" | "insert" | "list" | "block" | "history";
31
+ export interface RichTextToolbarItem {
32
+ id: RichTextToolbarItemId;
33
+ group: RichTextToolbarGroup;
34
+ /** Translation key, always `chelone.rich_text.<id>`. */
35
+ labelKey: `chelone.rich_text.${RichTextToolbarItemId}`;
36
+ defaultLabel: string;
37
+ /** A binding in the syntax of `matchesHotkey`, e.g. `"mod+b"`. */
38
+ shortcut?: string;
39
+ }
40
+ /**
41
+ * Every toolbar button, in display order. Adapters map each id to a tiptap
42
+ * command, so core names no tiptap type. A shortcut is listed only where
43
+ * tiptap's own keymap binds it, so the adapter wires nothing extra. Link has
44
+ * none: tiptap binds no key for it, and `mod+k` opens the command menu.
45
+ */
46
+ export declare const richTextToolbarItems: readonly Readonly<RichTextToolbarItem>[];
47
+ export type RichTextHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
48
+ export interface RichTextBlockType {
49
+ id: "paragraph" | `heading${RichTextHeadingLevel}`;
50
+ /** Set on the heading types. */
51
+ level?: RichTextHeadingLevel;
52
+ labelKey: string;
53
+ defaultLabel: string;
54
+ }
55
+ /** What the block-type select offers: paragraph, then headings 1 to 6. */
56
+ export declare const richTextBlockTypes: readonly Readonly<RichTextBlockType>[];
@@ -0,0 +1,105 @@
1
+ const CONTROL = /[\u0000-\u001f\u007f]/g;
2
+ const SCHEME = /^([a-z][a-z0-9+.-]*):/i;
3
+ const LINK_SCHEMES = /* @__PURE__ */ new Set([
4
+ "http",
5
+ "https",
6
+ "mailto",
7
+ "tel"
8
+ ]);
9
+ function normalize(url) {
10
+ return url.replace(CONTROL, "").trimStart();
11
+ }
12
+ function schemeOf(normalized) {
13
+ return SCHEME.exec(normalized)?.[1]?.toLowerCase();
14
+ }
15
+ function isSafeLinkUrl(url) {
16
+ const scheme = schemeOf(normalize(url));
17
+ return scheme === void 0 || LINK_SCHEMES.has(scheme);
18
+ }
19
+ const IMAGE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "blob"]);
20
+ const RASTER_DATA_URL = /^data:image\/(?:png|jpeg|gif|webp|avif);base64,/i;
21
+ function isSafeImageSrc(src, options = {}) {
22
+ const normalized = normalize(src);
23
+ const scheme = schemeOf(normalized);
24
+ if (scheme === void 0 || IMAGE_SCHEMES.has(scheme)) return true;
25
+ return options.allowDataImages === true && RASTER_DATA_URL.test(normalized);
26
+ }
27
+ const richTextSanitizeOptions = Object.freeze({
28
+ FORBID_TAGS: Object.freeze([
29
+ "style",
30
+ "form",
31
+ "input",
32
+ "button",
33
+ "textarea",
34
+ "select",
35
+ "iframe",
36
+ "object",
37
+ "embed"
38
+ ]),
39
+ FORBID_ATTR: Object.freeze(["style"])
40
+ });
41
+ const richTextStarterKitOptions = Object.freeze({
42
+ heading: Object.freeze({
43
+ levels: Object.freeze([1, 2, 3, 4, 5, 6])
44
+ }),
45
+ link: Object.freeze({
46
+ openOnClick: false,
47
+ autolink: true,
48
+ defaultProtocol: "https",
49
+ isAllowedUri: (url) => isSafeLinkUrl(url)
50
+ })
51
+ });
52
+ function item(id, group, defaultLabel, shortcut) {
53
+ return Object.freeze({
54
+ id,
55
+ group,
56
+ labelKey: `chelone.rich_text.${id}`,
57
+ defaultLabel,
58
+ ...shortcut ? { shortcut } : {}
59
+ });
60
+ }
61
+ const richTextToolbarItems = Object.freeze([
62
+ item("bold", "mark", "Bold", "mod+b"),
63
+ item("italic", "mark", "Italic", "mod+i"),
64
+ item("underline", "mark", "Underline", "mod+u"),
65
+ item("strike", "mark", "Strikethrough", "mod+shift+s"),
66
+ item("code", "mark", "Code", "mod+e"),
67
+ item("clearFormatting", "mark", "Clear formatting"),
68
+ item("link", "insert", "Link"),
69
+ item("bulletList", "list", "Bullet list", "mod+shift+8"),
70
+ item("orderedList", "list", "Ordered list", "mod+shift+7"),
71
+ item("blockquote", "block", "Blockquote", "mod+shift+b"),
72
+ item("codeBlock", "block", "Code block", "mod+alt+c"),
73
+ item("horizontalRule", "insert", "Horizontal rule"),
74
+ item("undo", "history", "Undo", "mod+z"),
75
+ item("redo", "history", "Redo", "mod+shift+z")
76
+ ]);
77
+ function heading(level) {
78
+ return Object.freeze({
79
+ id: `heading${level}`,
80
+ level,
81
+ labelKey: `chelone.rich_text.heading_${level}`,
82
+ defaultLabel: `Heading ${level}`
83
+ });
84
+ }
85
+ const richTextBlockTypes = Object.freeze([
86
+ Object.freeze({
87
+ id: "paragraph",
88
+ labelKey: "chelone.rich_text.paragraph",
89
+ defaultLabel: "Paragraph"
90
+ }),
91
+ heading(1),
92
+ heading(2),
93
+ heading(3),
94
+ heading(4),
95
+ heading(5),
96
+ heading(6)
97
+ ]);
98
+ export {
99
+ isSafeImageSrc,
100
+ isSafeLinkUrl,
101
+ richTextBlockTypes,
102
+ richTextSanitizeOptions,
103
+ richTextStarterKitOptions,
104
+ richTextToolbarItems
105
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chelone/core",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,12 +44,12 @@
44
44
  "zod": "^3.25.0"
45
45
  },
46
46
  "dependencies": {
47
- "dot.paths": "^0.2.0"
47
+ "dot.paths": "^0.2.1"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "vite build && tsc -p tsconfig.build.json && node ../../scripts/dts-extensions.mjs dist",
51
51
  "check:pack": "publint --pack pnpm --strict && d=$(mktemp -d) && trap 'rm -rf $d' EXIT && pnpm pack --pack-destination $d && attw $d/*.tgz --profile esm-only",
52
52
  "test": "NODE_OPTIONS=--no-experimental-webstorage vitest run --typecheck",
53
- "typecheck": "tsc --noEmit"
53
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.es2020.json"
54
54
  }
55
55
  }