@hidemikimura/chit-ui 0.1.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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +440 -0
  3. package/dist/chit-ui.iife.min.js +964 -0
  4. package/dist/chit-ui.iife.min.js.map +1 -0
  5. package/dist/chit-ui.min.js +964 -0
  6. package/dist/chit-ui.min.js.map +1 -0
  7. package/dist/types/bundle.d.ts +7 -0
  8. package/dist/types/chit-ui.d.ts +251 -0
  9. package/dist/types/controllers/breakpoint-controller.d.ts +29 -0
  10. package/dist/types/controllers/composer-controller.d.ts +54 -0
  11. package/dist/types/controllers/scroll-controller.d.ts +44 -0
  12. package/dist/types/controllers/state-controller.d.ts +61 -0
  13. package/dist/types/controllers/theme-controller.d.ts +47 -0
  14. package/dist/types/element.d.ts +1 -0
  15. package/dist/types/events.d.ts +28 -0
  16. package/dist/types/global.d.ts +25 -0
  17. package/dist/types/i18n/labels.d.ts +68 -0
  18. package/dist/types/index.d.ts +4 -0
  19. package/dist/types/render/composer.d.ts +15 -0
  20. package/dist/types/render/content.d.ts +75 -0
  21. package/dist/types/render/launcher.d.ts +18 -0
  22. package/dist/types/render/message-list.d.ts +17 -0
  23. package/dist/types/render/message.d.ts +14 -0
  24. package/dist/types/render/panel.d.ts +10 -0
  25. package/dist/types/styles/adopted-sheet.d.ts +20 -0
  26. package/dist/types/styles/composer.css.d.ts +1 -0
  27. package/dist/types/styles/content.css.d.ts +9 -0
  28. package/dist/types/styles/host.css.d.ts +9 -0
  29. package/dist/types/styles/launcher.css.d.ts +1 -0
  30. package/dist/types/styles/message.css.d.ts +1 -0
  31. package/dist/types/styles/panel.css.d.ts +1 -0
  32. package/dist/types/theme/default-theme.d.ts +97 -0
  33. package/dist/types/theme/merge-theme.d.ts +33 -0
  34. package/dist/types/theme/theme-to-css.d.ts +24 -0
  35. package/dist/types/types.d.ts +268 -0
  36. package/package.json +71 -0
  37. package/src/bundle.js +11 -0
  38. package/src/chit-ui.js +548 -0
  39. package/src/controllers/breakpoint-controller.js +82 -0
  40. package/src/controllers/composer-controller.js +215 -0
  41. package/src/controllers/scroll-controller.js +252 -0
  42. package/src/controllers/state-controller.js +316 -0
  43. package/src/controllers/theme-controller.js +75 -0
  44. package/src/element.js +4 -0
  45. package/src/events.js +33 -0
  46. package/src/global.d.ts +25 -0
  47. package/src/i18n/labels.js +72 -0
  48. package/src/index.js +9 -0
  49. package/src/render/composer.js +72 -0
  50. package/src/render/content.js +211 -0
  51. package/src/render/launcher.js +64 -0
  52. package/src/render/message-list.js +67 -0
  53. package/src/render/message.js +82 -0
  54. package/src/render/panel.js +77 -0
  55. package/src/styles/adopted-sheet.js +72 -0
  56. package/src/styles/composer.css.js +108 -0
  57. package/src/styles/content.css.js +119 -0
  58. package/src/styles/host.css.js +160 -0
  59. package/src/styles/launcher.css.js +139 -0
  60. package/src/styles/message.css.js +192 -0
  61. package/src/styles/panel.css.js +126 -0
  62. package/src/theme/default-theme.js +111 -0
  63. package/src/theme/merge-theme.js +100 -0
  64. package/src/theme/theme-to-css.js +94 -0
  65. package/src/types.js +143 -0
@@ -0,0 +1,119 @@
1
+ // @ts-check
2
+ import { css } from 'lit';
3
+
4
+ /**
5
+ * Everything that keeps arbitrary consumer HTML inside its bubble.
6
+ *
7
+ * The widget has no say over what goes in a message, so the containment is
8
+ * structural rather than a list of rules per element: the content box declares
9
+ * its own size, refuses to grow past the bubble, and contains its layout and
10
+ * paint so a stray position or margin cannot escape.
11
+ */
12
+ export const contentStyles = css`
13
+ [part~='message-content'] {
14
+ min-width: 0;
15
+ max-width: 100%;
16
+ /* Long URLs and unbroken words wrap instead of widening the bubble. */
17
+ overflow-wrap: anywhere;
18
+ word-break: normal;
19
+ overflow-x: hidden;
20
+ /* Keeps a fixed or hugely-margined child from painting outside. */
21
+ contain: layout paint;
22
+ line-height: 1.5;
23
+ }
24
+
25
+ /*
26
+ * Plain text keeps its line breaks. Only this form: applying pre-wrap to
27
+ * html content would turn the newlines and indentation of the markup itself
28
+ * into visible blank lines.
29
+ */
30
+ [part~='message-content'][data-content='text'] {
31
+ white-space: pre-wrap;
32
+ }
33
+
34
+ [part~='message-content'] > :first-child {
35
+ margin-top: 0;
36
+ }
37
+ [part~='message-content'] > :last-child {
38
+ margin-bottom: 0;
39
+ }
40
+
41
+ /* Only direct children: a tooltip nested deeper still positions normally. */
42
+ [part~='message-content'] > * {
43
+ position: static !important;
44
+ }
45
+
46
+ [part~='message-content'] img,
47
+ [part~='message-content'] video,
48
+ [part~='message-content'] iframe,
49
+ [part~='message-content'] svg,
50
+ [part~='message-content'] canvas,
51
+ [part~='message-content'] picture,
52
+ [part~='message-content'] embed,
53
+ [part~='message-content'] object {
54
+ max-width: 100%;
55
+ height: auto;
56
+ display: block;
57
+ }
58
+
59
+ /* Wide content scrolls inside itself rather than stretching the bubble. */
60
+ [part~='message-content'] table {
61
+ display: block;
62
+ max-width: 100%;
63
+ overflow-x: auto;
64
+ border-collapse: collapse;
65
+ }
66
+
67
+ [part~='message-content'] pre {
68
+ max-width: 100%;
69
+ overflow-x: auto;
70
+ white-space: pre;
71
+ }
72
+
73
+ [part~='message-content'] code {
74
+ overflow-wrap: anywhere;
75
+ }
76
+
77
+ [part~='message-content'] a {
78
+ color: var(--chit-color-accent);
79
+ }
80
+
81
+ [part~='message-content'] button,
82
+ [part~='message-content'] input,
83
+ [part~='message-content'] select,
84
+ [part~='message-content'] textarea {
85
+ font: inherit;
86
+ max-width: 100%;
87
+ }
88
+
89
+ [part~='message-content'] h1,
90
+ [part~='message-content'] h2,
91
+ [part~='message-content'] h3,
92
+ [part~='message-content'] h4,
93
+ [part~='message-content'] h5,
94
+ [part~='message-content'] h6 {
95
+ font-size: 1.1em;
96
+ margin: 0.5em 0;
97
+ }
98
+
99
+ [part~='message-content'] p {
100
+ margin: 0.5em 0;
101
+ }
102
+
103
+ [part~='message-content'] ul,
104
+ [part~='message-content'] ol {
105
+ margin: 0.5em 0;
106
+ padding-inline-start: 1.5em;
107
+ }
108
+
109
+ [part~='message-content'] blockquote {
110
+ margin: 0.5em 0;
111
+ padding-inline-start: 0.75em;
112
+ border-inline-start: 3px solid var(--chit-color-border);
113
+ }
114
+
115
+ [part~='message-content'] hr {
116
+ border: 0;
117
+ border-top: 1px solid var(--chit-color-border);
118
+ }
119
+ `;
@@ -0,0 +1,160 @@
1
+ // @ts-check
2
+ import { css } from 'lit';
3
+
4
+ /**
5
+ * Host-level concerns: the coordinate space the widget lives in, the theme
6
+ * fallbacks, and the transition animations.
7
+ *
8
+ * Every custom property here is a fallback only. ThemeController writes the
9
+ * real values into an adopted stylesheet that sits ahead of this one, and page
10
+ * CSS (`chit-ui { --chit-color-accent: ... }`) beats both.
11
+ */
12
+ export const hostStyles = css`
13
+ :host {
14
+ --chit-z-index: 2147483000;
15
+ --chit-font-family: system-ui, -apple-system, 'Segoe UI', 'Hiragino Sans', 'Noto Sans JP', sans-serif;
16
+ --chit-font-size: 14px;
17
+
18
+ --chit-launcher-size: 60px;
19
+ --chit-launcher-offset-x: 24px;
20
+ --chit-launcher-offset-y: 24px;
21
+ --chit-launcher-radius: 30px;
22
+ --chit-launcher-bg: #1a73e8;
23
+ --chit-launcher-text: #ffffff;
24
+ --chit-launcher-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
25
+
26
+ --chit-panel-width: 380px;
27
+ --chit-panel-height: 600px;
28
+ --chit-panel-offset-x: 24px;
29
+ --chit-panel-offset-y: 24px;
30
+ --chit-panel-radius: 16px;
31
+ --chit-panel-shadow: 0 8px 32px rgba(0, 0, 0, 0.24);
32
+
33
+ --chit-color-bg: #ffffff;
34
+ --chit-color-text: #1f1f1f;
35
+ --chit-color-accent: #1a73e8;
36
+ --chit-color-border: #e4e4e7;
37
+ --chit-color-header-bg: #ffffff;
38
+ --chit-color-header-text: #1f1f1f;
39
+
40
+ position: fixed;
41
+ inset: 0;
42
+ z-index: var(--chit-z-index);
43
+ /* The host is only a coordinate space; clicks land on the page behind it. */
44
+ pointer-events: none;
45
+ font-family: var(--chit-font-family);
46
+ font-size: var(--chit-font-size);
47
+ color: var(--chit-color-text);
48
+ -webkit-font-smoothing: antialiased;
49
+ }
50
+
51
+ /*
52
+ * Keyed off what the DOM is showing, not off the state property: that flips
53
+ * to 'hidden' the moment it is assigned, while the exit animation still has
54
+ * to play.
55
+ */
56
+ :host([data-rendered-state='hidden']) {
57
+ display: none;
58
+ }
59
+
60
+ :host > *,
61
+ [part] {
62
+ pointer-events: auto;
63
+ }
64
+
65
+ *,
66
+ *::before,
67
+ *::after {
68
+ box-sizing: border-box;
69
+ }
70
+
71
+ /* --- transitions ------------------------------------------------------ */
72
+
73
+ [data-anim] {
74
+ animation-duration: var(--_chit-anim-duration, 200ms);
75
+ animation-fill-mode: both;
76
+ animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
77
+ }
78
+
79
+ [data-anim='enter'][data-effect='fade'] {
80
+ animation-name: chit-fade-in;
81
+ }
82
+ [data-anim='exit'][data-effect='fade'] {
83
+ animation-name: chit-fade-out;
84
+ }
85
+ [data-anim='enter'][data-effect='scale'] {
86
+ animation-name: chit-scale-in;
87
+ }
88
+ [data-anim='exit'][data-effect='scale'] {
89
+ animation-name: chit-scale-out;
90
+ }
91
+ [data-anim='enter'][data-effect='slide'] {
92
+ animation-name: chit-slide-in;
93
+ }
94
+ [data-anim='exit'][data-effect='slide'] {
95
+ animation-name: chit-slide-out;
96
+ }
97
+
98
+ @keyframes chit-fade-in {
99
+ from {
100
+ opacity: 0;
101
+ }
102
+ to {
103
+ opacity: 1;
104
+ }
105
+ }
106
+ @keyframes chit-fade-out {
107
+ from {
108
+ opacity: 1;
109
+ }
110
+ to {
111
+ opacity: 0;
112
+ }
113
+ }
114
+ @keyframes chit-scale-in {
115
+ from {
116
+ opacity: 0;
117
+ transform: scale(0.85);
118
+ }
119
+ to {
120
+ opacity: 1;
121
+ transform: none;
122
+ }
123
+ }
124
+ @keyframes chit-scale-out {
125
+ from {
126
+ opacity: 1;
127
+ transform: none;
128
+ }
129
+ to {
130
+ opacity: 0;
131
+ transform: scale(0.85);
132
+ }
133
+ }
134
+ @keyframes chit-slide-in {
135
+ from {
136
+ opacity: 0;
137
+ transform: translateY(16px);
138
+ }
139
+ to {
140
+ opacity: 1;
141
+ transform: none;
142
+ }
143
+ }
144
+ @keyframes chit-slide-out {
145
+ from {
146
+ opacity: 1;
147
+ transform: none;
148
+ }
149
+ to {
150
+ opacity: 0;
151
+ transform: translateY(16px);
152
+ }
153
+ }
154
+
155
+ @media (prefers-reduced-motion: reduce) {
156
+ [data-anim] {
157
+ animation: none !important;
158
+ }
159
+ }
160
+ `;
@@ -0,0 +1,139 @@
1
+ // @ts-check
2
+ import { css } from 'lit';
3
+
4
+ export const launcherStyles = css`
5
+ [part~='launcher'] {
6
+ position: absolute;
7
+ display: grid;
8
+ place-items: center;
9
+ width: var(--chit-launcher-size);
10
+ height: var(--chit-launcher-size);
11
+ padding: 0;
12
+ border: 0;
13
+ border-radius: var(--chit-launcher-radius);
14
+ background-color: var(--chit-launcher-bg);
15
+ background-position: center;
16
+ background-size: cover;
17
+ background-repeat: no-repeat;
18
+ color: var(--chit-launcher-text);
19
+ box-shadow: var(--chit-launcher-shadow);
20
+ font: inherit;
21
+ line-height: 1;
22
+ cursor: pointer;
23
+ transition: filter 120ms ease;
24
+ }
25
+
26
+ /* The image comes from the theme, so it is a background rather than an <img>. */
27
+ [part~='launcher'][data-has-image] {
28
+ background-image: var(--chit-launcher-image);
29
+ }
30
+
31
+ /*
32
+ * A consumer component draws the whole launcher, so the button stops being a
33
+ * visual of its own: no box to squeeze the component into, and none of the
34
+ * widget's own paint behind it. It keeps being a button, which is the part
35
+ * that matters for keyboards and screen readers.
36
+ */
37
+ [part~='launcher'][data-custom] {
38
+ width: auto;
39
+ height: auto;
40
+ padding: 0;
41
+ border-radius: 0;
42
+ background: none;
43
+ box-shadow: none;
44
+ color: inherit;
45
+ }
46
+
47
+ [part~='launcher'][data-custom]:hover {
48
+ filter: none;
49
+ }
50
+
51
+ [part~='launcher']:hover {
52
+ filter: brightness(1.06);
53
+ }
54
+
55
+ [part~='launcher']:focus-visible {
56
+ outline: 2px solid var(--chit-launcher-bg);
57
+ outline-offset: 3px;
58
+ }
59
+
60
+ [part~='launcher-icon'] {
61
+ width: 55%;
62
+ height: 55%;
63
+ }
64
+
65
+ [part~='launcher-label'] {
66
+ padding: 0 0.75em;
67
+ font-size: 0.95em;
68
+ white-space: nowrap;
69
+ }
70
+
71
+ /* --- placement -------------------------------------------------------- */
72
+
73
+ :host([data-launcher-position='bottom-right']) [part~='launcher'] {
74
+ right: var(--chit-launcher-offset-x);
75
+ bottom: var(--chit-launcher-offset-y);
76
+ transform-origin: bottom right;
77
+ }
78
+ :host([data-launcher-position='bottom-left']) [part~='launcher'] {
79
+ left: var(--chit-launcher-offset-x);
80
+ bottom: var(--chit-launcher-offset-y);
81
+ transform-origin: bottom left;
82
+ }
83
+ :host([data-launcher-position='top-right']) [part~='launcher'] {
84
+ right: var(--chit-launcher-offset-x);
85
+ top: var(--chit-launcher-offset-y);
86
+ transform-origin: top right;
87
+ }
88
+ :host([data-launcher-position='top-left']) [part~='launcher'] {
89
+ left: var(--chit-launcher-offset-x);
90
+ top: var(--chit-launcher-offset-y);
91
+ transform-origin: top left;
92
+ }
93
+
94
+ /* --- idle motion ------------------------------------------------------ */
95
+
96
+ /*
97
+ * Only while nothing else is animating: a transition sets data-anim and owns
98
+ * the animation property for as long as it runs.
99
+ */
100
+ :host([data-idle='pulse']) [part~='launcher']:not([data-anim]) {
101
+ animation: chit-idle-pulse 2.4s ease-in-out infinite;
102
+ }
103
+ :host([data-idle='bounce']) [part~='launcher']:not([data-anim]) {
104
+ animation: chit-idle-bounce 2.4s ease-in-out infinite;
105
+ }
106
+
107
+ @keyframes chit-idle-pulse {
108
+ 0%,
109
+ 70%,
110
+ 100% {
111
+ box-shadow: var(--chit-launcher-shadow);
112
+ }
113
+ 35% {
114
+ box-shadow:
115
+ var(--chit-launcher-shadow),
116
+ 0 0 0 10px color-mix(in srgb, var(--chit-launcher-bg) 22%, transparent);
117
+ }
118
+ }
119
+
120
+ @keyframes chit-idle-bounce {
121
+ 0%,
122
+ 62%,
123
+ 100% {
124
+ transform: translateY(0);
125
+ }
126
+ 72% {
127
+ transform: translateY(-6px);
128
+ }
129
+ 86% {
130
+ transform: translateY(-2px);
131
+ }
132
+ }
133
+
134
+ @media (prefers-reduced-motion: reduce) {
135
+ :host([data-idle]) [part~='launcher']:not([data-anim]) {
136
+ animation: none;
137
+ }
138
+ }
139
+ `;
@@ -0,0 +1,192 @@
1
+ // @ts-check
2
+ import { css } from 'lit';
3
+
4
+ export const messageStyles = css`
5
+ [part~='messages'] {
6
+ flex: 1 1 auto;
7
+ min-height: 0;
8
+ overflow-y: auto;
9
+ overscroll-behavior: contain;
10
+ scrollbar-width: thin;
11
+ }
12
+
13
+ [part~='messages-inner'] {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 0.75em;
17
+ padding: 1em;
18
+ }
19
+
20
+ /*
21
+ * The row spans the full width and the bubble is capped inside it. Letting
22
+ * the row shrink to fit instead would make the bubble's percentage cap
23
+ * resolve against a shrink-to-fit parent, and because the content sets
24
+ * overflow-wrap: anywhere its min-content width is one character — so the
25
+ * bubble collapsed to a single column of letters.
26
+ */
27
+ [part~='message'] {
28
+ display: flex;
29
+ align-items: flex-end;
30
+ gap: 0.5em;
31
+ width: 100%;
32
+ max-width: 100%;
33
+ }
34
+
35
+ [part~='message'] .body {
36
+ display: flex;
37
+ min-width: 0;
38
+ max-width: 78%;
39
+ flex-direction: column;
40
+ gap: 0.25em;
41
+ }
42
+
43
+ /* Reversed row: the bubble sits at the right edge without shrinking the row. */
44
+ [part~='message-user'] {
45
+ flex-direction: row-reverse;
46
+ }
47
+
48
+ [part~='message-user'] .body {
49
+ align-items: flex-end;
50
+ }
51
+
52
+ [part~='bubble'] {
53
+ position: relative;
54
+ padding: 0.6em 0.85em;
55
+ border-radius: 14px;
56
+ background: var(--chit-color-assistant-bg);
57
+ color: var(--chit-color-assistant-text);
58
+ }
59
+
60
+ [part~='message-user'] [part~='bubble'] {
61
+ background: var(--chit-color-user-bg);
62
+ color: var(--chit-color-user-text);
63
+ border-bottom-right-radius: 4px;
64
+ }
65
+
66
+ [part~='message-assistant'] [part~='bubble'] {
67
+ border-bottom-left-radius: 4px;
68
+ }
69
+
70
+ [part~='avatar'] {
71
+ flex: none;
72
+ width: 2em;
73
+ height: 2em;
74
+ border-radius: 50%;
75
+ object-fit: cover;
76
+ }
77
+
78
+ [part~='name'] {
79
+ font-size: 0.8em;
80
+ color: var(--chit-color-system-text);
81
+ }
82
+
83
+ [part~='meta'] {
84
+ display: flex;
85
+ gap: 0.5em;
86
+ font-size: 0.75em;
87
+ color: var(--chit-color-system-text);
88
+ }
89
+
90
+ [part~='status'][data-status='error'] {
91
+ color: #d93025;
92
+ }
93
+
94
+ [part~='message-system'] {
95
+ justify-content: center;
96
+ font-size: 0.8em;
97
+ color: var(--chit-color-system-text);
98
+ text-align: center;
99
+ }
100
+
101
+ /* --- streaming and typing --------------------------------------------- */
102
+
103
+ [part~='cursor'] {
104
+ display: inline-block;
105
+ width: 0.5em;
106
+ height: 1em;
107
+ margin-inline-start: 0.15em;
108
+ background: currentColor;
109
+ opacity: 0.6;
110
+ vertical-align: text-bottom;
111
+ animation: chit-caret 1s step-end infinite;
112
+ }
113
+
114
+ @keyframes chit-caret {
115
+ 50% {
116
+ opacity: 0;
117
+ }
118
+ }
119
+
120
+ [part~='typing'] {
121
+ align-self: flex-start;
122
+ padding: 0.7em 0.9em;
123
+ border-radius: 14px;
124
+ border-bottom-left-radius: 4px;
125
+ background: var(--chit-color-assistant-bg);
126
+ color: var(--chit-color-assistant-text);
127
+ }
128
+
129
+ [part~='typing'] .dots {
130
+ display: inline-flex;
131
+ gap: 0.25em;
132
+ }
133
+
134
+ [part~='typing'] .dots i {
135
+ width: 0.4em;
136
+ height: 0.4em;
137
+ border-radius: 50%;
138
+ background: currentColor;
139
+ opacity: 0.4;
140
+ animation: chit-typing 1.2s ease-in-out infinite;
141
+ }
142
+
143
+ [part~='typing'] .dots i:nth-child(2) {
144
+ animation-delay: 0.15s;
145
+ }
146
+ [part~='typing'] .dots i:nth-child(3) {
147
+ animation-delay: 0.3s;
148
+ }
149
+
150
+ @keyframes chit-typing {
151
+ 0%,
152
+ 60%,
153
+ 100% {
154
+ opacity: 0.35;
155
+ transform: translateY(0);
156
+ }
157
+ 30% {
158
+ opacity: 1;
159
+ transform: translateY(-3px);
160
+ }
161
+ }
162
+
163
+ /* --- jump to latest ---------------------------------------------------- */
164
+
165
+ [part~='to-latest'] {
166
+ position: absolute;
167
+ left: 50%;
168
+ bottom: 5.5em;
169
+ transform: translateX(-50%);
170
+ padding: 0.35em 0.9em;
171
+ border: 1px solid var(--chit-color-border);
172
+ border-radius: 999px;
173
+ background: var(--chit-color-bg);
174
+ color: var(--chit-color-text);
175
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.16);
176
+ font: inherit;
177
+ font-size: 0.8em;
178
+ cursor: pointer;
179
+ }
180
+
181
+ [part~='to-latest']:focus-visible {
182
+ outline: 2px solid var(--chit-color-accent);
183
+ outline-offset: 2px;
184
+ }
185
+
186
+ @media (prefers-reduced-motion: reduce) {
187
+ [part~='cursor'],
188
+ [part~='typing'] .dots i {
189
+ animation: none;
190
+ }
191
+ }
192
+ `;