phoenix_live_view 1.2.0-rc.0 → 1.2.0-rc.2

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.
@@ -54,7 +54,11 @@ export default class DOMPatch {
54
54
  afterphxChildAdded: [],
55
55
  aftertransitionsDiscarded: [],
56
56
  };
57
- this.withChildren = opts.withChildren || opts.undoRef || false;
57
+ // unlock patches pass undoRef and must morph the locked element itself, not
58
+ // only its children. The first client ref is 0, so this must check for the
59
+ // option's presence rather than truthiness.
60
+ this.withChildren =
61
+ opts.withChildren || opts.undoRef !== undefined || false;
58
62
  this.undoRef = opts.undoRef;
59
63
  }
60
64
 
@@ -105,6 +109,12 @@ export default class DOMPatch {
105
109
  targetContainer = clonedTree.querySelector(
106
110
  `[data-phx-component="${this.targetCID}"]`,
107
111
  );
112
+ // The visible DOM can still contain the target CID while the locked
113
+ // clone has gone stale and no longer does. In that case there is no
114
+ // safe clone target for this component diff, so leave the visible DOM
115
+ // locked and wait for a later patch instead of throwing or patching
116
+ // outside the locked tree.
117
+ if (!targetContainer) return;
108
118
  }
109
119
  }
110
120
  }
@@ -148,6 +158,13 @@ export default class DOMPatch {
148
158
  if (isJoinPatch) {
149
159
  return node.id;
150
160
  }
161
+
162
+ // If ID was touched by JavaScript hook, use PHX_MAGIC_ID for matching.
163
+ // This ensures morphdom can match elements even when JS modifies their IDs.
164
+ if (DOM.private(node, "clientsideIdAttribute")) {
165
+ return node.getAttribute && node.getAttribute(PHX_MAGIC_ID);
166
+ }
167
+
151
168
  return (
152
169
  node.id || (node.getAttribute && node.getAttribute(PHX_MAGIC_ID))
153
170
  );
@@ -309,7 +326,16 @@ export default class DOMPatch {
309
326
  phxViewportBottom,
310
327
  );
311
328
  DOM.cleanChildNodes(toEl, phxUpdate);
329
+ const isFocusedFormEl =
330
+ focused && fromEl.isSameNode(focused) && DOM.isFormInput(fromEl);
331
+ const focusedSelectChanged =
332
+ isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
312
333
  if (this.skipCIDSibling(toEl)) {
334
+ // A skipped update returns before the normal update path below, so
335
+ // it must still perform the lock bookkeeping that keeps private
336
+ // cloned trees in sync.
337
+ this.maybeCloneLockedElement(fromEl, isFocusedFormEl);
338
+ this.copyNestedPrivateLock(fromEl, toEl);
313
339
  // if this is a live component used in a stream, we may need to reorder it
314
340
  this.maybeReOrderStream(fromEl);
315
341
  return false;
@@ -354,30 +380,7 @@ export default class DOMPatch {
354
380
  // We keep a reference to the cloned tree in the element's private data, and
355
381
  // on ack (view.undoRefs), we morph the cloned tree with the true fromEl in the DOM to
356
382
  // apply any changes that happened while the element was locked.
357
- const isFocusedFormEl =
358
- focused && fromEl.isSameNode(focused) && DOM.isFormInput(fromEl);
359
- const focusedSelectChanged =
360
- isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
361
- if (fromEl.hasAttribute(PHX_REF_SRC)) {
362
- const ref = new ElementRef(fromEl);
363
- // only perform the clone step if this is not a patch that unlocks
364
- if (
365
- ref.lockRef &&
366
- (!this.undoRef || !ref.isLockUndoneBy(this.undoRef))
367
- ) {
368
- DOM.applyStickyOperations(fromEl);
369
- const isLocked = fromEl.hasAttribute(PHX_REF_LOCK);
370
- const clone = isLocked
371
- ? DOM.private(fromEl, PHX_REF_LOCK) || fromEl.cloneNode(true)
372
- : null;
373
- if (clone) {
374
- DOM.putPrivate(fromEl, PHX_REF_LOCK, clone);
375
- if (!isFocusedFormEl) {
376
- fromEl = clone;
377
- }
378
- }
379
- }
380
- }
383
+ fromEl = this.maybeCloneLockedElement(fromEl, isFocusedFormEl);
381
384
 
382
385
  // nested view handling
383
386
  if (DOM.isPhxChild(toEl)) {
@@ -391,14 +394,10 @@ export default class DOMPatch {
391
394
  return false;
392
395
  }
393
396
 
394
- // if we are undoing a lock, copy potentially nested clones over
395
- if (this.undoRef && DOM.private(toEl, PHX_REF_LOCK)) {
396
- DOM.putPrivate(
397
- fromEl,
398
- PHX_REF_LOCK,
399
- DOM.private(toEl, PHX_REF_LOCK),
400
- );
401
- }
397
+ // If we are undoing a lock, copy potentially nested clones over.
398
+ // This keeps an inner locked subtree's private clone alive while an
399
+ // ancestor lock is being reconciled.
400
+ this.copyNestedPrivateLock(fromEl, toEl);
402
401
  // now copy regular DOM.private data
403
402
  DOM.copyPrivates(toEl, fromEl);
404
403
 
@@ -649,18 +648,26 @@ export default class DOMPatch {
649
648
  }
650
649
 
651
650
  if (streamAt === 0) {
652
- el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
651
+ this.moveOrInsertBefore(
652
+ el.parentElement,
653
+ el,
654
+ el.parentElement.firstElementChild,
655
+ );
653
656
  } else if (streamAt > 0) {
654
657
  const children = Array.from(el.parentElement.children);
655
658
  const oldIndex = children.indexOf(el);
656
659
  if (streamAt >= children.length - 1) {
657
- el.parentElement.appendChild(el);
660
+ this.moveOrInsertBefore(el.parentElement, el, null);
658
661
  } else {
659
662
  const sibling = children[streamAt];
660
663
  if (oldIndex > streamAt) {
661
- el.parentElement.insertBefore(el, sibling);
664
+ this.moveOrInsertBefore(el.parentElement, el, sibling);
662
665
  } else {
663
- el.parentElement.insertBefore(el, sibling.nextElementSibling);
666
+ this.moveOrInsertBefore(
667
+ el.parentElement,
668
+ el,
669
+ sibling.nextElementSibling,
670
+ );
664
671
  }
665
672
  }
666
673
  }
@@ -668,6 +675,25 @@ export default class DOMPatch {
668
675
  this.maybeLimitStream(el);
669
676
  }
670
677
 
678
+ // Reorder a child within its parent. When supported, use the atomic
679
+ // moveBefore (https://developer.mozilla.org/en-US/docs/Web/API/Node/moveBefore)
680
+ // so connected custom elements (and other state-bearing nodes like iframes)
681
+ // are not disconnected and reconnected by the move. Falls back to
682
+ // insertBefore otherwise. Passing `ref === null` moves to the end.
683
+ // See also https://github.com/phoenixframework/phoenix_live_view/issues/4212.
684
+ moveOrInsertBefore(parent, child, ref) {
685
+ if (typeof parent.moveBefore === "function") {
686
+ try {
687
+ parent.moveBefore(child, ref);
688
+ return;
689
+ } catch {
690
+ // moveBefore can throw (e.g. HierarchyRequestError) in cases where
691
+ // an atomic move is not possible; fall back to insertBefore.
692
+ }
693
+ }
694
+ parent.insertBefore(child, ref);
695
+ }
696
+
671
697
  maybeLimitStream(el) {
672
698
  const { limit } = this.getStreamInsert(el);
673
699
  const children = limit !== null && Array.from(el.parentElement.children);
@@ -722,6 +748,39 @@ export default class DOMPatch {
722
748
  return el.nodeType === Node.ELEMENT_NODE && el.hasAttribute(PHX_SKIP);
723
749
  }
724
750
 
751
+ maybeCloneLockedElement(fromEl, isFocusedFormEl) {
752
+ if (!fromEl.hasAttribute(PHX_REF_SRC)) return fromEl;
753
+
754
+ const ref = new ElementRef(fromEl);
755
+ // Only perform the clone step while the element remains locked. lockRef can
756
+ // be 0 for the first event, so compare against null/undefined explicitly.
757
+ if (
758
+ ref.lockRef === null ||
759
+ (this.undoRef !== undefined && ref.isLockUndoneBy(this.undoRef))
760
+ ) {
761
+ return fromEl;
762
+ }
763
+
764
+ DOM.applyStickyOperations(fromEl);
765
+ const clone = fromEl.hasAttribute(PHX_REF_LOCK)
766
+ ? DOM.private(fromEl, PHX_REF_LOCK) || fromEl.cloneNode(true)
767
+ : null;
768
+ if (!clone) return fromEl;
769
+
770
+ DOM.putPrivate(fromEl, PHX_REF_LOCK, clone);
771
+ return isFocusedFormEl ? fromEl : clone;
772
+ }
773
+
774
+ copyNestedPrivateLock(fromEl, toEl) {
775
+ // During unlock morphs, toEl may be the private clone that accumulated a
776
+ // nested locked subtree. Copy that private clone back to fromEl before the
777
+ // outer unlock finishes so the nested element can apply its own ack later.
778
+ // undoRef can be 0, so presence is checked with undefined.
779
+ if (this.undoRef === undefined || !DOM.private(toEl, PHX_REF_LOCK)) return;
780
+
781
+ DOM.putPrivate(fromEl, PHX_REF_LOCK, DOM.private(toEl, PHX_REF_LOCK));
782
+ }
783
+
725
784
  targetCIDContainer(html) {
726
785
  if (!this.isCIDPatch()) {
727
786
  return;
@@ -46,10 +46,8 @@ const Hooks = {
46
46
  this.inputEl = document.getElementById(
47
47
  this.el.getAttribute(PHX_UPLOAD_REF),
48
48
  );
49
- LiveUploader.getEntryDataURL(this.inputEl, this.ref, (url) => {
50
- this.url = url;
51
- this.el.src = url;
52
- });
49
+ this.url = LiveUploader.getEntryDataURL(this.inputEl, this.ref);
50
+ this.el.src = this.url;
53
51
  },
54
52
  destroyed() {
55
53
  URL.revokeObjectURL(this.url);
@@ -264,7 +262,7 @@ Hooks.InfiniteScroll = {
264
262
  updated() {
265
263
  // Check if the scroll container still exists
266
264
  // https://github.com/phoenixframework/phoenix_live_view/issues/4169.
267
- if (!this.scrollContainer.isConnected) {
265
+ if (this.scrollContainer && !this.scrollContainer.isConnected) {
268
266
  this.destroyed();
269
267
  this.mounted();
270
268
  }
@@ -15,6 +15,7 @@ import { logError } from "./utils";
15
15
  import type { EncodedJS, LiveSocketJSCommands } from "./js_commands";
16
16
  import type { Hook, HooksOptions } from "./view_hook";
17
17
  import type { Socket as PhoenixSocket } from "phoenix";
18
+ import LiveUploader from "./live_uploader";
18
19
 
19
20
  /**
20
21
  * Options for configuring the LiveSocket instance.
@@ -350,4 +351,32 @@ function createHook(el: HTMLElement, callbacks: Hook): ViewHook {
350
351
  return hook;
351
352
  }
352
353
 
353
- export { LiveSocket, isUsedInput, createHook, ViewHook, Hook, HooksOptions };
354
+ /** Returns an object URL for the file matching the given upload ref,
355
+ * or `null` if no matching file is found.
356
+ *
357
+ * @param input - The file input element associated with the upload.
358
+ * @param uploadRef - The upload ref identifying the file entry.
359
+ *
360
+ * @example
361
+ *
362
+ * import { getFileURLForUpload } from "phoenix_live_view"
363
+ *
364
+ * let url = getFileURLForUpload(inputEl, uploadRef)
365
+ * if (url) { imgEl.src = url }
366
+ */
367
+ function getFileURLForUpload(
368
+ input: HTMLElement,
369
+ uploadRef: string,
370
+ ): string | null {
371
+ return LiveUploader.getEntryDataURL(input, uploadRef);
372
+ }
373
+
374
+ export {
375
+ LiveSocket,
376
+ isUsedInput,
377
+ createHook,
378
+ ViewHook,
379
+ Hook,
380
+ HooksOptions,
381
+ getFileURLForUpload,
382
+ };
@@ -600,6 +600,11 @@ const JS = {
600
600
  .filter((attr) => !alteredAttrs.includes(attr))
601
601
  .concat(removes);
602
602
 
603
+ // If element ID is touched via JavaScript, mark it for cheap lookup during morphdom
604
+ if (sets.some(([attr, _val]) => attr === "id")) {
605
+ DOM.putPrivate(el, "clientsideIdAttribute", true);
606
+ }
607
+
603
608
  DOM.putSticky(el, "attrs", (currentEl) => {
604
609
  newRemoves.forEach((attr) => currentEl.removeAttribute(attr));
605
610
  newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val));
@@ -22,11 +22,12 @@ export default class LiveUploader {
22
22
  }
23
23
  }
24
24
 
25
- static getEntryDataURL(inputEl, ref, callback) {
25
+ static getEntryDataURL(inputEl, ref) {
26
26
  const file = this.activeFiles(inputEl).find(
27
27
  (file) => this.genFileRef(file) === ref,
28
28
  );
29
- callback(URL.createObjectURL(file));
29
+ if (!file) return null;
30
+ return URL.createObjectURL(file);
30
31
  }
31
32
 
32
33
  static hasUploadsInProgress(formEl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phoenix_live_view",
3
- "version": "1.2.0-rc.0",
3
+ "version": "1.2.0-rc.2",
4
4
  "description": "The Phoenix LiveView JavaScript client.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,6 +22,7 @@ __export(phoenix_live_view_exports, {
22
22
  LiveSocket: () => LiveSocket2,
23
23
  ViewHook: () => ViewHook,
24
24
  createHook: () => createHook,
25
+ getFileURLForUpload: () => getFileURLForUpload,
25
26
  isUsedInput: () => isUsedInput
26
27
  });
27
28
  module.exports = __toCommonJS(phoenix_live_view_exports);
@@ -1061,11 +1062,13 @@ var LiveUploader = class _LiveUploader {
1061
1062
  return file._phxRef;
1062
1063
  }
1063
1064
  }
1064
- static getEntryDataURL(inputEl, ref, callback) {
1065
+ static getEntryDataURL(inputEl, ref) {
1065
1066
  const file = this.activeFiles(inputEl).find(
1066
1067
  (file2) => this.genFileRef(file2) === ref
1067
1068
  );
1068
- callback(URL.createObjectURL(file));
1069
+ if (!file)
1070
+ return null;
1071
+ return URL.createObjectURL(file);
1069
1072
  }
1070
1073
  static hasUploadsInProgress(formEl) {
1071
1074
  let active = 0;
@@ -1292,10 +1295,8 @@ var Hooks = {
1292
1295
  this.inputEl = document.getElementById(
1293
1296
  this.el.getAttribute(PHX_UPLOAD_REF)
1294
1297
  );
1295
- LiveUploader.getEntryDataURL(this.inputEl, this.ref, (url) => {
1296
- this.url = url;
1297
- this.el.src = url;
1298
- });
1298
+ this.url = LiveUploader.getEntryDataURL(this.inputEl, this.ref);
1299
+ this.el.src = this.url;
1299
1300
  },
1300
1301
  destroyed() {
1301
1302
  URL.revokeObjectURL(this.url);
@@ -1460,7 +1461,7 @@ Hooks.InfiniteScroll = {
1460
1461
  }
1461
1462
  },
1462
1463
  updated() {
1463
- if (!this.scrollContainer.isConnected) {
1464
+ if (this.scrollContainer && !this.scrollContainer.isConnected) {
1464
1465
  this.destroyed();
1465
1466
  this.mounted();
1466
1467
  }
@@ -2271,7 +2272,7 @@ var DOMPatch = class {
2271
2272
  afterphxChildAdded: [],
2272
2273
  aftertransitionsDiscarded: []
2273
2274
  };
2274
- this.withChildren = opts.withChildren || opts.undoRef || false;
2275
+ this.withChildren = opts.withChildren || opts.undoRef !== void 0 || false;
2275
2276
  this.undoRef = opts.undoRef;
2276
2277
  }
2277
2278
  before(kind, callback) {
@@ -2310,6 +2311,8 @@ var DOMPatch = class {
2310
2311
  targetContainer = clonedTree.querySelector(
2311
2312
  `[data-phx-component="${this.targetCID}"]`
2312
2313
  );
2314
+ if (!targetContainer)
2315
+ return;
2313
2316
  }
2314
2317
  }
2315
2318
  }
@@ -2338,6 +2341,9 @@ var DOMPatch = class {
2338
2341
  if (isJoinPatch) {
2339
2342
  return node.id;
2340
2343
  }
2344
+ if (dom_default.private(node, "clientsideIdAttribute")) {
2345
+ return node.getAttribute && node.getAttribute(PHX_MAGIC_ID);
2346
+ }
2341
2347
  return node.id || node.getAttribute && node.getAttribute(PHX_MAGIC_ID);
2342
2348
  },
2343
2349
  // skip indexing from children when container is stream
@@ -2459,7 +2465,11 @@ var DOMPatch = class {
2459
2465
  phxViewportBottom
2460
2466
  );
2461
2467
  dom_default.cleanChildNodes(toEl, phxUpdate);
2468
+ const isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
2469
+ const focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
2462
2470
  if (this.skipCIDSibling(toEl)) {
2471
+ this.maybeCloneLockedElement(fromEl, isFocusedFormEl);
2472
+ this.copyNestedPrivateLock(fromEl, toEl);
2463
2473
  this.maybeReOrderStream(fromEl);
2464
2474
  return false;
2465
2475
  }
@@ -2487,22 +2497,7 @@ var DOMPatch = class {
2487
2497
  if (fromEl.type === "number" && fromEl.validity && fromEl.validity.badInput) {
2488
2498
  return false;
2489
2499
  }
2490
- const isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
2491
- const focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl);
2492
- if (fromEl.hasAttribute(PHX_REF_SRC)) {
2493
- const ref = new ElementRef(fromEl);
2494
- if (ref.lockRef && (!this.undoRef || !ref.isLockUndoneBy(this.undoRef))) {
2495
- dom_default.applyStickyOperations(fromEl);
2496
- const isLocked = fromEl.hasAttribute(PHX_REF_LOCK);
2497
- const clone2 = isLocked ? dom_default.private(fromEl, PHX_REF_LOCK) || fromEl.cloneNode(true) : null;
2498
- if (clone2) {
2499
- dom_default.putPrivate(fromEl, PHX_REF_LOCK, clone2);
2500
- if (!isFocusedFormEl) {
2501
- fromEl = clone2;
2502
- }
2503
- }
2504
- }
2505
- }
2500
+ fromEl = this.maybeCloneLockedElement(fromEl, isFocusedFormEl);
2506
2501
  if (dom_default.isPhxChild(toEl)) {
2507
2502
  const prevSession = fromEl.getAttribute(PHX_SESSION);
2508
2503
  dom_default.mergeAttrs(fromEl, toEl, { exclude: [PHX_STATIC] });
@@ -2513,13 +2508,7 @@ var DOMPatch = class {
2513
2508
  dom_default.applyStickyOperations(fromEl);
2514
2509
  return false;
2515
2510
  }
2516
- if (this.undoRef && dom_default.private(toEl, PHX_REF_LOCK)) {
2517
- dom_default.putPrivate(
2518
- fromEl,
2519
- PHX_REF_LOCK,
2520
- dom_default.private(toEl, PHX_REF_LOCK)
2521
- );
2522
- }
2511
+ this.copyNestedPrivateLock(fromEl, toEl);
2523
2512
  dom_default.copyPrivates(toEl, fromEl);
2524
2513
  if (dom_default.isPortalTemplate(toEl)) {
2525
2514
  portalCallbacks.push(() => this.teleport(toEl, morph));
@@ -2701,23 +2690,47 @@ var DOMPatch = class {
2701
2690
  return;
2702
2691
  }
2703
2692
  if (streamAt === 0) {
2704
- el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
2693
+ this.moveOrInsertBefore(
2694
+ el.parentElement,
2695
+ el,
2696
+ el.parentElement.firstElementChild
2697
+ );
2705
2698
  } else if (streamAt > 0) {
2706
2699
  const children = Array.from(el.parentElement.children);
2707
2700
  const oldIndex = children.indexOf(el);
2708
2701
  if (streamAt >= children.length - 1) {
2709
- el.parentElement.appendChild(el);
2702
+ this.moveOrInsertBefore(el.parentElement, el, null);
2710
2703
  } else {
2711
2704
  const sibling = children[streamAt];
2712
2705
  if (oldIndex > streamAt) {
2713
- el.parentElement.insertBefore(el, sibling);
2706
+ this.moveOrInsertBefore(el.parentElement, el, sibling);
2714
2707
  } else {
2715
- el.parentElement.insertBefore(el, sibling.nextElementSibling);
2708
+ this.moveOrInsertBefore(
2709
+ el.parentElement,
2710
+ el,
2711
+ sibling.nextElementSibling
2712
+ );
2716
2713
  }
2717
2714
  }
2718
2715
  }
2719
2716
  this.maybeLimitStream(el);
2720
2717
  }
2718
+ // Reorder a child within its parent. When supported, use the atomic
2719
+ // moveBefore (https://developer.mozilla.org/en-US/docs/Web/API/Node/moveBefore)
2720
+ // so connected custom elements (and other state-bearing nodes like iframes)
2721
+ // are not disconnected and reconnected by the move. Falls back to
2722
+ // insertBefore otherwise. Passing `ref === null` moves to the end.
2723
+ // See also https://github.com/phoenixframework/phoenix_live_view/issues/4212.
2724
+ moveOrInsertBefore(parent, child, ref) {
2725
+ if (typeof parent.moveBefore === "function") {
2726
+ try {
2727
+ parent.moveBefore(child, ref);
2728
+ return;
2729
+ } catch {
2730
+ }
2731
+ }
2732
+ parent.insertBefore(child, ref);
2733
+ }
2721
2734
  maybeLimitStream(el) {
2722
2735
  const { limit } = this.getStreamInsert(el);
2723
2736
  const children = limit !== null && Array.from(el.parentElement.children);
@@ -2758,6 +2771,25 @@ var DOMPatch = class {
2758
2771
  skipCIDSibling(el) {
2759
2772
  return el.nodeType === Node.ELEMENT_NODE && el.hasAttribute(PHX_SKIP);
2760
2773
  }
2774
+ maybeCloneLockedElement(fromEl, isFocusedFormEl) {
2775
+ if (!fromEl.hasAttribute(PHX_REF_SRC))
2776
+ return fromEl;
2777
+ const ref = new ElementRef(fromEl);
2778
+ if (ref.lockRef === null || this.undoRef !== void 0 && ref.isLockUndoneBy(this.undoRef)) {
2779
+ return fromEl;
2780
+ }
2781
+ dom_default.applyStickyOperations(fromEl);
2782
+ const clone2 = fromEl.hasAttribute(PHX_REF_LOCK) ? dom_default.private(fromEl, PHX_REF_LOCK) || fromEl.cloneNode(true) : null;
2783
+ if (!clone2)
2784
+ return fromEl;
2785
+ dom_default.putPrivate(fromEl, PHX_REF_LOCK, clone2);
2786
+ return isFocusedFormEl ? fromEl : clone2;
2787
+ }
2788
+ copyNestedPrivateLock(fromEl, toEl) {
2789
+ if (this.undoRef === void 0 || !dom_default.private(toEl, PHX_REF_LOCK))
2790
+ return;
2791
+ dom_default.putPrivate(fromEl, PHX_REF_LOCK, dom_default.private(toEl, PHX_REF_LOCK));
2792
+ }
2761
2793
  targetCIDContainer(html) {
2762
2794
  if (!this.isCIDPatch()) {
2763
2795
  return;
@@ -3695,6 +3727,9 @@ var JS = {
3695
3727
  const alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);
3696
3728
  const newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);
3697
3729
  const newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);
3730
+ if (sets.some(([attr, _val]) => attr === "id")) {
3731
+ dom_default.putPrivate(el, "clientsideIdAttribute", true);
3732
+ }
3698
3733
  dom_default.putSticky(el, "attrs", (currentEl) => {
3699
3734
  newRemoves.forEach((attr) => currentEl.removeAttribute(attr));
3700
3735
  newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val));
@@ -5952,7 +5987,7 @@ var LiveSocket = class {
5952
5987
  }
5953
5988
  // public
5954
5989
  version() {
5955
- return "1.2.0-rc.0";
5990
+ return "1.2.0-rc.2";
5956
5991
  }
5957
5992
  isProfileEnabled() {
5958
5993
  return this.sessionStorage.getItem(PHX_LV_PROFILE) === "true";
@@ -7003,4 +7038,7 @@ function createHook(el, callbacks) {
7003
7038
  dom_default.putCustomElHook(el, hook);
7004
7039
  return hook;
7005
7040
  }
7041
+ function getFileURLForUpload(input, uploadRef) {
7042
+ return LiveUploader.getEntryDataURL(input, uploadRef);
7043
+ }
7006
7044
  //# sourceMappingURL=phoenix_live_view.cjs.js.map