composeai 0.1.5 → 0.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "composeai",
3
- "version": "0.1.5",
3
+ "version": "0.1.8",
4
4
  "description": "The modern React composer for AI applications — a drop-in Lexical-powered chat input with markdown, mentions, slash commands, attachments, voice, streaming stop, and opt-in plugins for copilots, chatbots, and agent UIs.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
package/src/composer.css CHANGED
@@ -47,6 +47,17 @@
47
47
  font-family: var(--composer-font-family, inherit);
48
48
  }
49
49
 
50
+ /* iOS Safari zooms the whole page when a focused field's font-size is below
51
+ * 16px. On touch devices, floor the editor (and its placeholder) at 16px so
52
+ * tapping the composer never triggers that zoom — while still honouring a
53
+ * LARGER `--composer-font-size` the consumer may have set, via `max()`. */
54
+ @media (pointer: coarse) {
55
+ [data-composer-root] .composer-editor,
56
+ [data-composer-root] .composer-placeholder {
57
+ font-size: max(16px, var(--composer-font-size, 15px));
58
+ }
59
+ }
60
+
50
61
  .composer-editor {
51
62
  position: relative;
52
63
  display: block;
@@ -252,9 +263,18 @@
252
263
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
253
264
  font-size: 13px;
254
265
  line-height: 1.55;
255
- background: hsl(var(--muted));
266
+ /* Translucent so the live diagram watermark (.composer-mermaid-backdrop)
267
+ * behind the fence shows through while the source stays readable. */
268
+ background: hsl(var(--muted) / 0.6);
256
269
  padding: 0.125rem 0.625rem;
257
270
  border-inline-start: 3px solid hsl(var(--primary) / 0.4);
271
+ /* The card is rounded (--composer-radius) but not `overflow: hidden` (that
272
+ * would clip the upward popovers). The editor's own padding is small, so a
273
+ * full-bleed code block's square corners poke past the card's rounded
274
+ * corners. Inset the block horizontally and break long tokens so the grey
275
+ * fill always stays inside the card outline. */
276
+ margin-inline: 0.5rem;
277
+ overflow-wrap: anywhere;
258
278
  }
259
279
  .composer-paragraph[data-md-block^="code-"] + .composer-paragraph[data-md-block^="code-"] {
260
280
  margin-top: 0;
@@ -284,6 +304,31 @@
284
304
  margin-inline-end: 0.5rem;
285
305
  }
286
306
 
307
+ /* Syntax highlighting for fenced code (mermaid). Each token is a CodeTokenNode
308
+ * with a `--<kind>` class. Colours are overridable via `--composer-code-*`
309
+ * custom properties; the defaults read on both light and the translucent code
310
+ * fill. `ident` / `text` deliberately inherit the editor foreground. */
311
+ .composer-code-tok--keyword {
312
+ color: var(--composer-code-keyword, hsl(280 58% 58%));
313
+ font-weight: 600;
314
+ }
315
+ .composer-code-tok--arrow {
316
+ color: var(--composer-code-arrow, hsl(190 72% 38%));
317
+ }
318
+ .composer-code-tok--string {
319
+ color: var(--composer-code-string, hsl(150 46% 38%));
320
+ }
321
+ .composer-code-tok--comment {
322
+ color: var(--composer-code-comment, hsl(var(--muted-foreground)));
323
+ font-style: italic;
324
+ }
325
+ .composer-code-tok--number {
326
+ color: var(--composer-code-number, hsl(28 78% 48%));
327
+ }
328
+ .composer-code-tok--punct {
329
+ color: var(--composer-code-punct, hsl(var(--muted-foreground)));
330
+ }
331
+
287
332
  .composer-paragraph[data-md-block="hr"] {
288
333
  position: relative;
289
334
  color: transparent;
@@ -500,7 +545,7 @@
500
545
  margin-top: 0;
501
546
  }
502
547
 
503
- /* The MermaidSlot is already gated out by the React tree when multiline is
548
+ /* The mermaid preview is already gated out by the React tree when multiline is
504
549
  * false, but this defensive rule keeps any stray `[data-mermaid-tile]` from
505
550
  * showing if a consumer renders one manually inside an inline composer. */
506
551
  [data-composer-root][data-composer-inline] [data-mermaid-tile] {
@@ -536,6 +581,10 @@
536
581
  * ------------------------------------------------------------------------- */
537
582
  .composer-root,
538
583
  [data-composer-scope] {
584
+ /* Light is the baseline. Pinned explicitly so the composer's own native
585
+ * UI (caret, scrollbars) stays light even when the user's OS prefers dark
586
+ * but the host app is rendering light. */
587
+ color-scheme: light;
539
588
  --background: 0 0% 100%;
540
589
  --foreground: 222 18% 18%;
541
590
  --card: 0 0% 100%;
@@ -564,12 +613,23 @@
564
613
  0 20px 48px -20px hsl(220 13% 10% / 0.4);
565
614
  }
566
615
 
567
- /* Auto dark theme for hosts whose users prefer dark keeps the composer
568
- * readable out of the box on a dark canvas. A consumer who wants an explicit
569
- * theme regardless of OS setting passes `tokens` (which wins inline). */
570
- @media (prefers-color-scheme: dark) {
571
- .composer-root,
572
- [data-composer-scope] {
616
+ /* Dark theme applied when the HOST app signals dark via the conventional
617
+ * `.dark` class or `[data-theme="dark"]` on any ancestor (the Tailwind /
618
+ * shadcn convention), so the composer follows the APP's theme, NOT the OS.
619
+ * A composer on a light app whose user's OS prefers dark therefore stays
620
+ * light — the host is the source of truth.
621
+ *
622
+ * (Previously this was an `@media (prefers-color-scheme: dark)` query, which
623
+ * darkened the composer based on the OS even when the surrounding app was
624
+ * light — the mismatch this replaces.)
625
+ *
626
+ * Consumers with custom theming are unaffected: `color` / `tokens` / `sx`
627
+ * apply inline on the scope element and always win over this stylesheet. */
628
+ .dark .composer-root,
629
+ .dark [data-composer-scope],
630
+ [data-theme="dark"] .composer-root,
631
+ [data-theme="dark"] [data-composer-scope] {
632
+ color-scheme: dark;
573
633
  --background: 222 16% 11%;
574
634
  --foreground: 210 20% 92%;
575
635
  --card: 222 16% 13%;
@@ -596,7 +656,6 @@
596
656
  0 12px 32px -16px hsl(0 0% 0% / 0.6);
597
657
  --composer-shadow-pop: 0 4px 12px hsl(0 0% 0% / 0.5),
598
658
  0 20px 48px -20px hsl(0 0% 0% / 0.7);
599
- }
600
659
  }
601
660
 
602
661
  /* Shared keyframes / animation helpers. `composer-popover-in` already exists
@@ -749,6 +808,22 @@
749
808
  .composer-editor-block {
750
809
  position: relative;
751
810
  min-width: 0;
811
+ /* Own stacking context so the mermaid watermark (z-index: -1) tucks behind
812
+ * the editor text but still paints over the card background. */
813
+ isolation: isolate;
814
+ }
815
+
816
+ /* Live diagram watermark behind a ```mermaid code box. Measured + positioned
817
+ * imperatively (see DiagramBackdrop); we only theme it here. Sits beneath the
818
+ * translucent code fill and never intercepts clicks. */
819
+ .composer-mermaid-backdrop {
820
+ position: absolute;
821
+ z-index: -1;
822
+ pointer-events: none;
823
+ background-repeat: no-repeat;
824
+ background-position: center;
825
+ background-size: contain;
826
+ opacity: 0.16;
752
827
  }
753
828
  .composer-editor-block--inline {
754
829
  flex: 1 1 0%;
@@ -776,6 +851,37 @@
776
851
  text-overflow: ellipsis;
777
852
  }
778
853
 
854
+ /* Animated (typewriter) placeholder — a blinking caret trails the revealed
855
+ * text so the empty editor reads as if it's being typed into live. The caret
856
+ * is a pseudo-element so it never becomes part of the placeholder string. */
857
+ .composer-placeholder--animated::after {
858
+ content: "";
859
+ display: inline-block;
860
+ width: 1px;
861
+ height: 1em;
862
+ margin-inline-start: 1px;
863
+ vertical-align: text-bottom;
864
+ background: currentColor;
865
+ opacity: 0.8;
866
+ animation: composer-caret-blink 1.1s steps(1, end) infinite;
867
+ }
868
+ @keyframes composer-caret-blink {
869
+ 0%,
870
+ 50% {
871
+ opacity: 0.8;
872
+ }
873
+ 50.01%,
874
+ 100% {
875
+ opacity: 0;
876
+ }
877
+ }
878
+ @media (prefers-reduced-motion: reduce) {
879
+ .composer-placeholder--animated::after {
880
+ animation: none;
881
+ opacity: 0;
882
+ }
883
+ }
884
+
779
885
  .composer-inline-row {
780
886
  display: flex;
781
887
  align-items: center;
@@ -789,6 +895,83 @@
789
895
  align-items: center;
790
896
  }
791
897
 
898
+ /* ----------------------------------------------------------------------------
899
+ * Compact variant layout — the slim chat-bar: a single growable row of
900
+ * [ + ] [ editor ] [ voice · send ]. The "+" and the trailing cluster
901
+ * bottom-align so they stay pinned to the last line as the editor grows.
902
+ * ------------------------------------------------------------------------- */
903
+ .composer-compact-row {
904
+ display: flex;
905
+ align-items: flex-end;
906
+ gap: 0.25rem;
907
+ padding: 0.375rem 0.5rem;
908
+ }
909
+ .composer-compact-actions,
910
+ .composer-compact-send {
911
+ display: flex;
912
+ flex: none;
913
+ align-items: center;
914
+ gap: 0.25rem;
915
+ /* Pin to the bottom edge of the row so the controls line up with the last
916
+ * line of a multi-line draft (not floated to the vertical center). */
917
+ align-self: flex-end;
918
+ /* Keep a ~36px control band so single-line resting state lines up with the
919
+ * compact editor's min-height. */
920
+ min-height: 2.25rem;
921
+ }
922
+
923
+ /* Expanded compact bar — the actions footer beneath the full-width editor.
924
+ * `+` sits at the start, the voice·send cluster at the end. */
925
+ .composer-compact-footer {
926
+ display: flex;
927
+ align-items: center;
928
+ justify-content: space-between;
929
+ gap: 0.25rem;
930
+ padding: 0.125rem 0.5rem 0.375rem;
931
+ }
932
+
933
+ .composer-editor.composer-editor--compact {
934
+ min-height: 2.25rem; /* one line ≈ the control band height */
935
+ max-height: 12rem;
936
+ overflow-y: auto;
937
+ padding: 0.4375rem 0.5rem; /* ~7px top/bottom centers one line in 36px */
938
+ line-height: 1.5;
939
+ scrollbar-width: thin;
940
+ scrollbar-color: hsl(var(--border)) transparent;
941
+ }
942
+ .composer-editor--compact::-webkit-scrollbar {
943
+ width: 6px;
944
+ }
945
+ .composer-editor--compact::-webkit-scrollbar-thumb {
946
+ background: hsl(var(--border));
947
+ border-radius: 9999px;
948
+ }
949
+
950
+ .composer-placeholder--compact {
951
+ inset-inline: 0;
952
+ top: 0;
953
+ padding: 0.4375rem 0.5rem;
954
+ line-height: 1.5;
955
+ white-space: nowrap;
956
+ overflow: hidden;
957
+ text-overflow: ellipsis;
958
+ }
959
+
960
+ /* Quick-actions ("+") popover. Reuses .composer-attach-menu visuals; the
961
+ * trigger wrapper just needs relative positioning for the absolute menu, and
962
+ * any `toolbarExtras` get a divider above them. */
963
+ .composer-quick-actions {
964
+ position: relative;
965
+ }
966
+ .composer-quick-extras {
967
+ display: flex;
968
+ flex-direction: column;
969
+ gap: 0.25rem;
970
+ margin-top: 0.25rem;
971
+ padding-top: 0.25rem;
972
+ border-top: 1px solid hsl(var(--border));
973
+ }
974
+
792
975
  .composer-toolbar-row {
793
976
  display: flex;
794
977
  align-items: center;
@@ -822,6 +1005,18 @@
822
1005
  background: hsl(var(--accent));
823
1006
  color: hsl(var(--foreground));
824
1007
  }
1008
+ /* Toggle-style custom actions (active: true) light up in the brand accent.
1009
+ * The voice button's own pressed rule is more specific + later in the file,
1010
+ * so it still wins for the mic. */
1011
+ .composer-toolbar-btn[aria-pressed="true"],
1012
+ .composer-toolbar-btn[data-active] {
1013
+ background: hsl(var(--accent));
1014
+ color: hsl(var(--accent-foreground));
1015
+ }
1016
+ .composer-toolbar-btn:disabled {
1017
+ opacity: 0.45;
1018
+ cursor: default;
1019
+ }
825
1020
  .composer-toolbar-btn svg {
826
1021
  height: 1rem;
827
1022
  width: 1rem;
@@ -1406,6 +1601,14 @@
1406
1601
  border-top: 1px solid hsl(var(--border) / 0.6);
1407
1602
  background: hsl(var(--muted) / 0.3);
1408
1603
  padding: 0.75rem 1rem;
1604
+ /* The preview is the card's last, full-bleed child. The card is rounded but
1605
+ * isn't `overflow: hidden` (that would clip the upward-opening "+" /
1606
+ * attachment popovers), so without this the footer's square bottom corners
1607
+ * poke past the card's rounded edge. Match the card's inner radius (outer
1608
+ * radius minus the 1px border) so the footer tucks inside the corner. */
1609
+ border-end-start-radius: calc(var(--composer-radius, 28px) - 1px);
1610
+ border-end-end-radius: calc(var(--composer-radius, 28px) - 1px);
1611
+ overflow: hidden;
1409
1612
  }
1410
1613
  .composer-mermaid-head {
1411
1614
  display: flex;
@@ -1447,6 +1650,23 @@
1447
1650
  height: 6rem;
1448
1651
  width: 10rem;
1449
1652
  }
1653
+
1654
+ /* Compact variant: a slim strip of small thumbnails (no header banner). Each
1655
+ * tile is a "quick image" the user taps to open the full diagram in the
1656
+ * lightbox. */
1657
+ .composer-mermaid--compact {
1658
+ padding: 0.375rem 0.5rem;
1659
+ }
1660
+ .composer-mermaid--compact .composer-mermaid-svg {
1661
+ height: 2.75rem;
1662
+ width: 4.25rem;
1663
+ }
1664
+ .composer-mermaid--compact .composer-mermaid-zoom {
1665
+ height: 1rem;
1666
+ width: 1rem;
1667
+ inset-inline-end: 0.125rem;
1668
+ top: 0.125rem;
1669
+ }
1450
1670
  .composer-mermaid-svg svg {
1451
1671
  height: 100%;
1452
1672
  width: 100%;
@@ -1495,6 +1715,70 @@
1495
1715
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1496
1716
  }
1497
1717
 
1718
+ /* Compact variant: the diagram preview collapses behind a small live
1719
+ * thumbnail that sits beside the "+". A count badge appears when more than one
1720
+ * diagram is detected; pressing it opens the full tiles in an upward popover. */
1721
+ .composer-mermaid-trigger {
1722
+ position: relative;
1723
+ display: inline-flex;
1724
+ align-items: center;
1725
+ justify-content: center;
1726
+ height: 1.875rem;
1727
+ width: 2.75rem;
1728
+ padding: 0;
1729
+ overflow: hidden;
1730
+ border: 1px solid hsl(var(--border));
1731
+ border-radius: 0.5rem;
1732
+ background: hsl(var(--card));
1733
+ cursor: pointer;
1734
+ transition: border-color 0.15s ease;
1735
+ }
1736
+ .composer-mermaid-trigger:hover,
1737
+ .composer-mermaid-trigger[data-active] {
1738
+ border-color: hsl(var(--primary) / 0.4);
1739
+ }
1740
+ .composer-mermaid-thumb {
1741
+ display: block;
1742
+ height: 100%;
1743
+ width: 100%;
1744
+ }
1745
+ .composer-mermaid-thumb svg {
1746
+ height: 100%;
1747
+ width: 100%;
1748
+ object-fit: contain;
1749
+ }
1750
+ .composer-mermaid-count {
1751
+ position: absolute;
1752
+ top: -0.125rem;
1753
+ inset-inline-end: -0.125rem;
1754
+ display: flex;
1755
+ min-width: 0.875rem;
1756
+ height: 0.875rem;
1757
+ align-items: center;
1758
+ justify-content: center;
1759
+ padding-inline: 0.1875rem;
1760
+ border-radius: 9999px;
1761
+ background: hsl(var(--primary));
1762
+ color: hsl(var(--primary-foreground));
1763
+ font-size: 9px;
1764
+ font-weight: 600;
1765
+ line-height: 1;
1766
+ }
1767
+ .composer-mermaid-pop {
1768
+ position: absolute;
1769
+ bottom: 100%;
1770
+ inset-inline-start: 0;
1771
+ z-index: 30;
1772
+ margin-bottom: 0.5rem;
1773
+ max-width: min(80vw, 26rem);
1774
+ border-radius: 0.75rem;
1775
+ border: 1px solid hsl(var(--border));
1776
+ background: hsl(var(--popover));
1777
+ color: hsl(var(--popover-foreground));
1778
+ padding: 0.625rem 0.75rem;
1779
+ box-shadow: var(--composer-shadow-pop);
1780
+ }
1781
+
1498
1782
  /* ----------------------------------------------------------------------------
1499
1783
  * Hint bar + keyboard chips.
1500
1784
  * ------------------------------------------------------------------------- */