braid-text 0.3.24 → 0.3.25

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.
@@ -1,7 +1,5 @@
1
1
  // cursor-highlights.js — Render colored cursors and selections behind a <textarea>
2
2
  //
3
- // No dependencies. Pure DOM/CSS.
4
- //
5
3
  // Usage:
6
4
  // var hl = textarea_highlights(textarea)
7
5
  // hl.set('peer-1', [{ from: 5, to: 10, color: 'rgba(97,175,239,0.25)' }])
@@ -17,13 +15,13 @@ function textarea_highlights(textarea) {
17
15
  style.textContent = `
18
16
  .textarea-hl-backdrop {
19
17
  position: absolute;
20
- top: 0; left: 0; right: 0; bottom: 0;
21
18
  white-space: pre-wrap;
22
19
  word-wrap: break-word;
23
20
  overflow-y: auto;
24
21
  pointer-events: none;
25
22
  color: transparent;
26
23
  z-index: 1;
24
+ box-sizing: border-box;
27
25
  scrollbar-width: none;
28
26
  -ms-overflow-style: none;
29
27
  }
@@ -35,45 +33,34 @@ function textarea_highlights(textarea) {
35
33
  box-decoration-break: clone;
36
34
  }
37
35
  .textarea-hl-backdrop .cursor {
38
- position: relative;
39
- display: inline-block;
40
- width: 0;
41
- height: 1em;
42
- z-index: 10;
43
- }
44
- .textarea-hl-backdrop .cursor::before {
45
- content: '';
46
- position: absolute;
47
- left: -1px;
48
- top: 0;
49
- bottom: 0;
50
- width: 2px;
51
- background-color: var(--cursor-color, #ff5722);
52
- z-index: 10;
36
+ border-left: 2px solid var(--cursor-color, #ff5722);
37
+ margin-left: -1px;
38
+ margin-right: -1px;
53
39
  }
54
40
  `
55
41
  document.head.appendChild(style)
56
42
  }
57
43
 
58
- // Ensure textarea is wrapped in a position:relative container
59
- var wrap = textarea.parentElement
60
- if (getComputedStyle(wrap).position === 'static')
61
- wrap.style.position = 'relative'
44
+ // Save original styles so we can restore on destroy
45
+ var original_bg = textarea.style.backgroundColor
46
+ var original_position = textarea.style.position
47
+ var original_zIndex = textarea.style.zIndex
62
48
 
63
- // Move textarea's background to the wrapper so backdrops show through
49
+ // Read the textarea's background color before we make it transparent.
50
+ // Walk up the DOM if the textarea itself is transparent.
64
51
  var bg = getComputedStyle(textarea).backgroundColor
65
- if (!wrap.style.backgroundColor) {
66
- if (!bg || bg === 'rgba(0, 0, 0, 0)') {
67
- // Walk up the DOM to find the effective background
68
- var el = wrap
69
- while (el) {
70
- var elBg = getComputedStyle(el).backgroundColor
71
- if (elBg && elBg !== 'rgba(0, 0, 0, 0)') { bg = elBg; break }
72
- el = el.parentElement
73
- }
52
+ if (!bg || bg === 'rgba(0, 0, 0, 0)') {
53
+ var el = textarea.parentElement
54
+ while (el) {
55
+ var elBg = getComputedStyle(el).backgroundColor
56
+ if (elBg && elBg !== 'rgba(0, 0, 0, 0)') { bg = elBg; break }
57
+ el = el.parentElement
74
58
  }
75
- wrap.style.backgroundColor = bg || 'white'
76
59
  }
60
+ bg = bg || 'white'
61
+
62
+ // Make textarea transparent so backdrops show through.
63
+ // position:relative + z-index puts the textarea text above the backdrops.
77
64
  textarea.style.backgroundColor = 'transparent'
78
65
  textarea.style.position = 'relative'
79
66
  textarea.style.zIndex = '2'
@@ -95,7 +82,6 @@ function textarea_highlights(textarea) {
95
82
  var bg_height = test_span.getBoundingClientRect().height
96
83
  document.body.removeChild(test_div)
97
84
  var sel_pad = (line_height - bg_height) / 2
98
- wrap.style.setProperty('--sel-pad', sel_pad + 'px')
99
85
 
100
86
  // State
101
87
  var layer_data = {} // layer_id -> [{ from, to, color }]
@@ -110,16 +96,29 @@ function textarea_highlights(textarea) {
110
96
  }
111
97
  textarea.addEventListener('scroll', sync_scroll)
112
98
 
99
+ // Re-render when textarea resizes (user drag, window resize, CSS change)
100
+ var resize_observer = new ResizeObserver(do_render)
101
+ resize_observer.observe(textarea)
102
+
113
103
  // Build a backdrop style string matching the textarea's font/padding/border
114
104
  function backdrop_style() {
115
105
  var cs = getComputedStyle(textarea)
106
+ var bw = parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth)
107
+ var bh = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth)
108
+ // Use clientWidth/clientHeight (content + padding, excludes scrollbar)
109
+ // plus border, so the backdrop's content area matches the textarea's
110
+ // even when the textarea reserves space for a scrollbar.
116
111
  return 'font-family:' + cs.fontFamily + ';' +
117
112
  'font-size:' + cs.fontSize + ';' +
118
113
  'line-height:' + cs.lineHeight + ';' +
119
114
  'padding:' + cs.paddingTop + ' ' + cs.paddingRight + ' ' +
120
115
  cs.paddingBottom + ' ' + cs.paddingLeft + ';' +
121
116
  'border:' + cs.borderTopWidth + ' solid transparent;' +
122
- 'border-radius:' + cs.borderRadius + ';'
117
+ 'border-radius:' + cs.borderRadius + ';' +
118
+ 'width:' + (textarea.clientWidth + bw) + 'px;' +
119
+ 'height:' + (textarea.clientHeight + bh) + 'px;' +
120
+ '--sel-pad:' + sel_pad + 'px;' +
121
+ 'background-color:' + bg + ';'
123
122
  }
124
123
 
125
124
  function escape_html(text) {
@@ -192,13 +191,17 @@ function textarea_highlights(textarea) {
192
191
  }))
193
192
 
194
193
  if (!layer_divs[id]) {
194
+ // Insert backdrop as previous sibling of textarea.
195
+ // position:absolute takes it out of flow so it doesn't
196
+ // affect layout. Without top/left set, it naturally sits
197
+ // at the textarea's position.
195
198
  var div = document.createElement('div')
196
199
  div.className = 'textarea-hl-backdrop'
197
- wrap.insertBefore(div, textarea)
200
+ textarea.parentElement.insertBefore(div, textarea)
198
201
  layer_divs[id] = div
199
202
  }
200
203
 
201
- // Font/padding/border are set inline to match textarea;
204
+ // Font/padding/border/size are set inline to match textarea;
202
205
  // positioning/pointer-events/etc come from the CSS class.
203
206
  layer_divs[id].style.cssText = style_str
204
207
 
@@ -262,9 +265,14 @@ function textarea_highlights(textarea) {
262
265
  textarea.removeEventListener('scroll', sync_scroll)
263
266
  textarea.removeEventListener('blur', on_blur)
264
267
  textarea.removeEventListener('focus', on_focus)
268
+ resize_observer.disconnect()
265
269
  for (var div of Object.values(layer_divs)) div.remove()
266
270
  layer_data = {}
267
271
  layer_divs = {}
272
+ // Restore textarea styles
273
+ textarea.style.backgroundColor = original_bg
274
+ textarea.style.position = original_position
275
+ textarea.style.zIndex = original_zIndex
268
276
  }
269
277
  }
270
278
  }
@@ -325,11 +333,12 @@ function cursor_highlights(textarea, url, options) {
325
333
  if (destroyed) client.destroy()
326
334
  })
327
335
 
328
- document.addEventListener('selectionchange', function() {
336
+ function on_selectionchange() {
329
337
  if (applying_remote) return
330
338
  if (document.activeElement !== textarea) return
331
339
  if (client) client.set(textarea.selectionStart, textarea.selectionEnd)
332
- })
340
+ }
341
+ document.addEventListener('selectionchange', on_selectionchange)
333
342
 
334
343
  return {
335
344
  online: function() {
@@ -357,6 +366,7 @@ function cursor_highlights(textarea, url, options) {
357
366
 
358
367
  destroy: function() {
359
368
  destroyed = true
369
+ document.removeEventListener('selectionchange', on_selectionchange)
360
370
  if (client) client.destroy()
361
371
  hl.destroy()
362
372
  }
@@ -15,12 +15,12 @@
15
15
  // cursors.changed(patches)
16
16
  // cursors.destroy()
17
17
  //
18
- async function cursor_client(url, { peer, get_text, on_change, headers: user_headers }) {
18
+ async function cursor_client(url, { peer, get_text, on_change, headers: custom_headers }) {
19
19
  // --- feature detection: HEAD probe ---
20
20
  try {
21
21
  var head_res = await braid_fetch(url, {
22
22
  method: 'HEAD',
23
- headers: { ...user_headers, 'Accept': 'application/text-cursors+json' }
23
+ headers: { ...custom_headers, 'Accept': 'application/text-cursors+json' }
24
24
  })
25
25
  var ct = head_res.headers.get('content-type') || ''
26
26
  if (!ct.includes('application/text-cursors+json')) return null
@@ -124,7 +124,7 @@ async function cursor_client(url, { peer, get_text, on_change, headers: user_hea
124
124
  braid_fetch(url, {
125
125
  method: 'PUT',
126
126
  headers: {
127
- ...user_headers,
127
+ ...custom_headers,
128
128
  'Content-Type': 'application/text-cursors+json',
129
129
  Peer: peer,
130
130
  'Content-Range': 'json [' + JSON.stringify(peer) + ']',
@@ -156,7 +156,7 @@ async function cursor_client(url, { peer, get_text, on_change, headers: user_hea
156
156
  }},
157
157
  peer,
158
158
  headers: {
159
- ...user_headers,
159
+ ...custom_headers,
160
160
  Accept: 'application/text-cursors+json',
161
161
  Heartbeats: '10',
162
162
  },
@@ -106,7 +106,7 @@ function simpleton_client(url, {
106
106
  get_patches,
107
107
  get_state,
108
108
  content_type,
109
- headers: user_headers, // The user can pass in custom headers
109
+ headers: custom_headers, // The user can pass in custom headers
110
110
  // that are forwared into the fetch
111
111
  on_error,
112
112
  on_res,
@@ -144,7 +144,7 @@ function simpleton_client(url, {
144
144
  retry: () => true,
145
145
  parents: () => client_version.length ? client_version : null,
146
146
  onSubscriptionStatus: status => on_online && on_online(status.online),
147
- headers: { ...user_headers,
147
+ headers: { ...custom_headers,
148
148
  "Merge-Type": "simpleton",
149
149
  ...content_type && {Accept: content_type} },
150
150
  }).then(res => {
@@ -332,7 +332,7 @@ function simpleton_client(url, {
332
332
  peer, version, parents, patches,
333
333
  retry: (res) => res.status !== 550,
334
334
  headers: {
335
- ...user_headers,
335
+ ...custom_headers,
336
336
  "Merge-Type": "simpleton",
337
337
  ...send_digests && {
338
338
  "Repr-Digest": await get_digest(client_state) },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-text",
3
- "version": "0.3.24",
3
+ "version": "0.3.25",
4
4
  "description": "Library for collaborative text over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-text",