@homecode/ui 5.20.20 → 5.20.21

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/dist/esm/index.js CHANGED
@@ -24,7 +24,7 @@ export { Flex } from './src/components/Flex/Flex.js';
24
24
  export { FoldedLine } from './src/components/FoldedLine/FoldedLine.js';
25
25
  export { FoldedLineAction, FoldedLineActionsProvider, useFoldedLineActionsRegister } from './src/components/FoldedLine/FoldedLineActionsContext.js';
26
26
  export { Form } from './src/components/Form/Form.js';
27
- export { FormattedText } from './src/components/FormattedText/FormattedText.js';
27
+ export { FormattedText, findJsonEndExclusive } from './src/components/FormattedText/FormattedText.js';
28
28
  export { Gap } from './src/components/Gap/Gap.js';
29
29
  export { Gallery, normalizeGalleryItem } from './src/components/Gallery/Gallery.js';
30
30
  export { GridLayout } from './src/components/GridLayout/GridLayout.js';
@@ -108,7 +108,24 @@ function applyInjectors(text, injectors) {
108
108
  });
109
109
  return result;
110
110
  }
111
- function FormattedText({ text, className, onButtonClick, }) {
111
+ function FormattedText({ text, className, onButtonClick, quotes, injectors, breaks = true, }) {
112
+ // Without `<br>`s the text keeps its own newlines (pre-wrap), so a block, such
113
+ // as a table, swallows the blank lines after it instead of showing them.
114
+ const blankLinesAfter = (content, from) => breaks ? 0 : content.slice(from).match(/^\s*/)[0].length;
115
+ /** A run of `> ` lines, grayed with a bar down its left side. */
116
+ const injectQuote = (content) => {
117
+ const matches = content.match(/(?:^|\n)(>[^\n]*(?:\n>[^\n]*)*)(?:\n|$)/);
118
+ if (!matches)
119
+ return null;
120
+ const lines = matches[1]
121
+ .split('\n')
122
+ .map(line => line.replace(/^>[ \t]?/, ''));
123
+ return {
124
+ elem: (jsx("div", { className: S.quote, "data-quote": true, children: lines.join('\n') })),
125
+ index: matches.index,
126
+ length: matches[0].length,
127
+ };
128
+ };
112
129
  const injectLines = (content) => {
113
130
  const matches = content.match(/(^|\n)---[ \t]*(?:\n|$)/);
114
131
  if (!matches)
@@ -266,6 +283,7 @@ function FormattedText({ text, className, onButtonClick, }) {
266
283
  const headingTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
267
284
  const processCellContent = (value, depth = 0) => applyInjectors(value, [
268
285
  injectCodeBlock,
286
+ ...(injectors ?? []),
269
287
  injectPrettyJson,
270
288
  ...(depth === 0
271
289
  ? [
@@ -291,7 +309,10 @@ function FormattedText({ text, className, onButtonClick, }) {
291
309
  injectButtons,
292
310
  ]);
293
311
  const injectHeading = (content) => {
294
- const matches = content.match(/(^|\n)(#{1,6})\s+([^\n]+)/);
312
+ // Without `<br>`s the text keeps its own newlines (pre-wrap), so a heading,
313
+ // which is a block, swallows the blank lines around it instead of leaving
314
+ // an extra empty line.
315
+ const matches = content.match(breaks ? /(^|\n)(#{1,6})\s+([^\n]+)/ : /(^|\n+)(#{1,6})[ \t]+([^\n]+)\n*/);
295
316
  if (!matches)
296
317
  return null;
297
318
  const Tag = headingTags[Math.min(matches[2].length, 6) - 1];
@@ -350,13 +371,18 @@ function FormattedText({ text, className, onButtonClick, }) {
350
371
  return {
351
372
  elem: (jsx(Table, { size: "s", variant: "plain", columns: columns, data: tableData, className: S.table })),
352
373
  index: matches.index + (matches[1]?.length || 0),
353
- length: matches[0].length - (matches[1]?.length || 0),
374
+ length: matches[0].length -
375
+ (matches[1]?.length || 0) +
376
+ blankLinesAfter(content, matches.index + matches[0].length),
354
377
  };
355
378
  };
356
379
  return (jsx("div", { className: className, children: applyInjectors(text, [
357
380
  injectTables,
358
381
  injectCodeBlock,
382
+ ...(injectors ?? []),
359
383
  injectPrettyJson,
384
+ // After code, so a `> ` line inside a code block stays code.
385
+ ...(quotes ? [injectQuote] : []),
360
386
  injectHeading,
361
387
  injectLines,
362
388
  injectLinks,
@@ -365,8 +391,8 @@ function FormattedText({ text, className, onButtonClick, }) {
365
391
  injectBullet,
366
392
  injectNumbered,
367
393
  injectButtons,
368
- injectBreaks,
394
+ ...(breaks ? [injectBreaks] : []),
369
395
  ]) }));
370
396
  }
371
397
 
372
- export { FormattedText, hrefForFormattedLink };
398
+ export { FormattedText, findJsonEndExclusive, hrefForFormattedLink };
@@ -1,5 +1,5 @@
1
- var css_248z = ".FormattedText_bullet__vbqlq{display:inline-block;margin-bottom:2px;margin-left:var(--p-2);margin-right:2px}.FormattedText_bullet__vbqlq:before{background-color:var(--accent-color);border-radius:50%;content:\"\";display:block;height:6px;margin:0 4px;width:6px}.FormattedText_numberedIndex__rZ6Kt{font-feature-settings:\"tnum\";color:var(--accent-color);display:inline-block;font-variant-numeric:tabular-nums;margin-left:var(--p-2);margin-right:6px;min-width:1.25em}.FormattedText_line__c2bmE{background-color:var(--accent-color-alpha-200);border-radius:2px;height:2px;margin:var(--p-2) 0;width:100%}.FormattedText_codeBlock__Xrbvn,.FormattedText_codeInline__jCbNL{background-color:var(--decent-color-alpha-400);border-radius:var(--p-2);box-shadow:0 1px 1px var(--accent-color-alpha-50),inset 0 1px 1px var(--decent-color-alpha-500);font-family:monospace;-webkit-user-select:text;-moz-user-select:text;user-select:text}.FormattedText_codeInline__jCbNL{border-radius:3px;display:inline;font-size:90%;padding:0 4px}.FormattedText_codeBlock__Xrbvn,.FormattedText_jsonPre__qOGmo{display:block;font-size:90%;margin:var(--p-2) 0;overflow-x:auto;padding:var(--p-2);white-space:pre-wrap}.FormattedText_jsonPre__qOGmo{background-color:var(--decent-color-alpha-300);border-radius:var(--p-2);box-shadow:0 1px 1px var(--accent-color-alpha-50),inset 0 1px 1px var(--accent-color-alpha-50);font-family:monospace;max-width:100%;-webkit-user-select:text;-moz-user-select:text;user-select:text}.FormattedText_table__ipMMS{margin-bottom:10px}";
2
- var S = {"bullet":"FormattedText_bullet__vbqlq","numberedIndex":"FormattedText_numberedIndex__rZ6Kt","line":"FormattedText_line__c2bmE","codeInline":"FormattedText_codeInline__jCbNL","codeBlock":"FormattedText_codeBlock__Xrbvn","jsonPre":"FormattedText_jsonPre__qOGmo","table":"FormattedText_table__ipMMS"};
1
+ var css_248z = ".FormattedText_bullet__vbqlq{display:inline-block;margin-bottom:2px;margin-left:var(--p-2);margin-right:2px}.FormattedText_bullet__vbqlq:before{background-color:var(--accent-color);border-radius:50%;content:\"\";display:block;height:6px;margin:0 4px;width:6px}.FormattedText_numberedIndex__rZ6Kt{font-feature-settings:\"tnum\";color:var(--accent-color);display:inline-block;font-variant-numeric:tabular-nums;margin-left:var(--p-2);margin-right:6px;min-width:1.25em}.FormattedText_quote__BBm-M{color:var(--accent-color-alpha-600);font-size:85%;margin:0 0 var(--p-1);padding:2px 0 2px calc(4px + var(--p-2));position:relative;white-space:pre-wrap}.FormattedText_quote__BBm-M:before{background-color:var(--accent-color);border-radius:2px;bottom:2px;content:\"\";left:2px;opacity:.3;position:absolute;top:2px;width:4px}.FormattedText_line__c2bmE{background-color:var(--accent-color-alpha-200);border-radius:2px;height:2px;margin:var(--p-2) 0;width:100%}.FormattedText_codeBlock__Xrbvn,.FormattedText_codeInline__jCbNL{background-color:var(--decent-color-alpha-400);border-radius:var(--p-2);box-shadow:0 1px 1px var(--accent-color-alpha-50),inset 0 1px 1px var(--decent-color-alpha-500);font-family:monospace;-webkit-user-select:text;-moz-user-select:text;user-select:text}.FormattedText_codeInline__jCbNL{border-radius:3px;display:inline;font-size:90%;padding:0 4px}.FormattedText_codeBlock__Xrbvn,.FormattedText_jsonPre__qOGmo{display:block;font-size:90%;margin:var(--p-2) 0;overflow-x:auto;padding:var(--p-2);white-space:pre-wrap}.FormattedText_jsonPre__qOGmo{background-color:var(--decent-color-alpha-300);border-radius:var(--p-2);box-shadow:0 1px 1px var(--accent-color-alpha-50),inset 0 1px 1px var(--accent-color-alpha-50);font-family:monospace;max-width:100%;-webkit-user-select:text;-moz-user-select:text;user-select:text}.FormattedText_table__ipMMS{margin-bottom:10px}";
2
+ var S = {"bullet":"FormattedText_bullet__vbqlq","numberedIndex":"FormattedText_numberedIndex__rZ6Kt","quote":"FormattedText_quote__BBm-M","line":"FormattedText_line__c2bmE","codeInline":"FormattedText_codeInline__jCbNL","codeBlock":"FormattedText_codeBlock__Xrbvn","jsonPre":"FormattedText_jsonPre__qOGmo","table":"FormattedText_table__ipMMS"};
3
3
  if (typeof document !== 'undefined') {
4
4
  var __uilibHead = document.head || document.getElementsByTagName('head')[0];
5
5
  if (__uilibHead) {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Placeholder text for the composer's empty paragraphs. Only a wholly empty
3
+ * composer shows it: a quote or list with an empty line after it already is
4
+ * content, so it keeps no hint.
5
+ */
6
+ function promptComposerPlaceholder(text) {
7
+ return ({ editor }) => (editor.isEmpty ? text : '');
8
+ }
9
+
10
+ export { promptComposerPlaceholder };
@@ -3,6 +3,7 @@ import { Placeholder } from '@tiptap/extensions';
3
3
  import { useEditor } from '@tiptap/react';
4
4
  import StarterKit from '@tiptap/starter-kit';
5
5
  import S from './PromptComposer.styl.js';
6
+ import { promptComposerPlaceholder } from './promptComposerPlaceholder.js';
6
7
  import { createPromptBlocksExtension, isInPromptBlock } from '../../tiptap/prompt-blocks/createPromptBlocksExtension.js';
7
8
  import { promptBlocksDoc } from '../../tiptap/prompt-blocks/promptBlocksText.js';
8
9
  import { createLinkHighlightExtension } from '../../tiptap/link-highlight/createLinkHighlightExtension.js';
@@ -110,7 +111,7 @@ function usePromptComposerEditor({ disabled, placeholder, slashCommandItems, sla
110
111
  }),
111
112
  NonInclusiveLink,
112
113
  Placeholder.configure({
113
- placeholder: placeholderText,
114
+ placeholder: promptComposerPlaceholder(placeholderText),
114
115
  showOnlyWhenEditable: true,
115
116
  emptyNodeClass: S.promptComposerEmptyNode,
116
117
  }),
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
2
  import type { FormattedTextProps } from './FormattedText.types';
3
3
  export declare function hrefForFormattedLink(url: string): string;
4
- export declare function FormattedText({ text, className, onButtonClick, }: FormattedTextProps): React.JSX.Element;
4
+ export declare function findJsonEndExclusive(content: string, start: number): number;
5
+ export declare function FormattedText({ text, className, onButtonClick, quotes, injectors, breaks, }: FormattedTextProps): React.JSX.Element;
@@ -1,3 +1,10 @@
1
+ import type React from 'react';
2
+ export type InjectionResult = {
3
+ elem: React.ReactNode;
4
+ index: number;
5
+ length: number;
6
+ } | null;
7
+ export type Injector = (content: string) => InjectionResult;
1
8
  export type FormattedTextProps = {
2
9
  text: string;
3
10
  className?: string;
@@ -5,4 +12,7 @@ export type FormattedTextProps = {
5
12
  text: string;
6
13
  [name: string]: string;
7
14
  }) => void;
15
+ quotes?: boolean;
16
+ injectors?: Injector[];
17
+ breaks?: boolean;
8
18
  };
@@ -1,2 +1,2 @@
1
- export { FormattedText } from './FormattedText';
2
- export type { FormattedTextProps } from './FormattedText.types';
1
+ export { FormattedText, findJsonEndExclusive } from './FormattedText';
2
+ export type { FormattedTextProps, InjectionResult, Injector, } from './FormattedText.types';
@@ -0,0 +1,9 @@
1
+ import type { Editor } from '@tiptap/core';
2
+ /**
3
+ * Placeholder text for the composer's empty paragraphs. Only a wholly empty
4
+ * composer shows it: a quote or list with an empty line after it already is
5
+ * content, so it keeps no hint.
6
+ */
7
+ export declare function promptComposerPlaceholder(text: string): ({ editor }: {
8
+ editor: Editor;
9
+ }) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homecode/ui",
3
- "version": "5.20.20",
3
+ "version": "5.20.21",
4
4
  "description": "React UI components library",
5
5
  "scripts": {
6
6
  "tests": "jest",