From c057f97c0235269d7ff4145463155f05de071483 Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Tue, 23 Apr 2024 13:38:32 +0200
Subject: [PATCH 01/11] :sparkles: Start implementing fov as an astropy.Angle

---
 js/widget.js              | 31 ++++++++++++++++++-------------
 src/ipyaladin/__init__.py | 27 +++++++++++++++++++++++++--
 2 files changed, 43 insertions(+), 15 deletions(-)

diff --git a/js/widget.js b/js/widget.js
index 8be72e91..8e8d5579 100644
--- a/js/widget.js
+++ b/js/widget.js
@@ -37,6 +37,7 @@ function render({ model, el }) {
 
   const ra_dec = init_options["target"].split(" ");
   aladin.gotoRaDec(ra_dec[0], ra_dec[1]);
+  aladin.setFoV(init_options["fov"]);
 
   el.appendChild(aladinDiv);
 
@@ -85,24 +86,25 @@ function render({ model, el }) {
   let fov_js = false;
 
   aladin.on("zoomChanged", (fov) => {
-    if (!fov_py) {
-      fov_js = true;
-      // fov MUST be cast into float in order to be sent to the model
-      model.set("fov", parseFloat(fov.toFixed(5)));
-      model.save_changes();
-    } else {
+    if (fov_py) {
       fov_py = false;
+      return;
     }
+    fov_js = true;
+    // fov MUST be cast into float in order to be sent to the model
+    model.set("_fov", parseFloat(fov.toFixed(5)));
+    model.set("shared_fov", parseFloat(fov.toFixed(5)));
+    model.save_changes();
   });
 
-  model.on("change:fov", () => {
-    if (!fov_js) {
-      fov_py = true;
-      let fov = model.get("fov");
-      aladin.setFoV(fov);
-    } else {
+  model.on("change:shared_fov", () => {
+    if (fov_js) {
       fov_js = false;
+      return;
     }
+    fov_py = true;
+    let fov = model.get("shared_fov");
+    aladin.setFoV(fov);
   });
 
   /* Div control */
@@ -193,6 +195,9 @@ function render({ model, el }) {
   model.on("msg:custom", (msg) => {
     let options = {};
     switch (msg["event_name"]) {
+      case "change_fov":
+        aladin.setFoV(msg["fov"]);
+        break;
       case "goto_ra_dec":
         const ra = msg["ra"];
         const dec = msg["dec"];
@@ -252,7 +257,7 @@ function render({ model, el }) {
   return () => {
     // need to unsubscribe the listeners
     model.off("change:shared_target");
-    model.off("change:fov");
+    model.off("change:shared_fov");
     model.off("change:height");
     model.off("change:coo_frame");
     model.off("change:survey");
diff --git a/src/ipyaladin/__init__.py b/src/ipyaladin/__init__.py
index 6c9fbe99..beec5611 100644
--- a/src/ipyaladin/__init__.py
+++ b/src/ipyaladin/__init__.py
@@ -4,7 +4,7 @@
 import warnings
 
 import anywidget
-from astropy.coordinates import SkyCoord
+from astropy.coordinates import SkyCoord, Angle
 from traitlets import (
     Float,
     Int,
@@ -43,7 +43,8 @@ class Aladin(anywidget.AnyWidget):
         help="A trait that can be used with `~ipywidgets.widgets.jslink`"
         "to link two Aladin Lite widgets targets together",
     ).tag(sync=True)
-    fov = Float(60.0).tag(sync=True, init_option=True)
+    _fov = Float(60.0).tag(sync=True, init_option=True)
+    shared_fov = Float(60.0).tag(sync=True)
     survey = Unicode("https://alaskybis.unistra.fr/DSS/DSSColor").tag(
         sync=True, init_option=True
     )
@@ -99,6 +100,7 @@ def _init_options(self):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.target = kwargs.get("target", "0 0")
+        self.fov = kwargs.get("fov", 60.0)
         self.on_msg(self._handle_custom_message)
 
     def _handle_custom_message(self, model, message, list_of_buffers):  # noqa: ARG002
@@ -119,6 +121,27 @@ def _handle_custom_message(self, model, message, list_of_buffers):  # noqa: ARG0
         elif event_type == "select" and "select" in self.listener_callback:
             self.listener_callback["select"](message_content)
 
+    @property
+    def fov(self) -> Angle:
+        """The field of view of the Aladin Lite widget.
+
+        It can be set with either a float or an `~astropy.units.Angle` object.
+
+        Returns
+        -------
+        Angle
+            An astropy.units.Angle object representing the field of view.
+
+        """
+        return Angle(self._fov, unit="deg")
+
+    @fov.setter
+    def fov(self, fov: Union[float, Angle]):
+        if isinstance(fov, Angle):
+            fov = fov.deg
+        self._fov = fov
+        self.send({"event_name": "change_fov", "fov": fov})
+
     @property
     def target(self) -> SkyCoord:
         """The target of the Aladin Lite widget.

From c41251044f6f0f86872a9567024c47c938aee695 Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Thu, 25 Apr 2024 10:18:12 +0200
Subject: [PATCH 02/11] :arrow_up: Update Aladin Lite version from 3.3.3 to
 3.4.0

---
 js/widget.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/js/widget.js b/js/widget.js
index 8e8d5579..093ba153 100644
--- a/js/widget.js
+++ b/js/widget.js
@@ -1,4 +1,4 @@
-import A from "https://esm.sh/aladin-lite@3.3.3-beta";
+import A from "https://esm.sh/aladin-lite@3.4.0-beta";
 import "./widget.css";
 
 let idxView = 0;

From 580c372356ae7d191f9c500bcfda45d10502ceea Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Thu, 25 Apr 2024 10:20:17 +0200
Subject: [PATCH 03/11] :memo: Update example 6 to replace fov by shared_fov

---
 examples/6_Linked-widgets.ipynb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/examples/6_Linked-widgets.ipynb b/examples/6_Linked-widgets.ipynb
index 966b5fc4..f31212f9 100644
--- a/examples/6_Linked-widgets.ipynb
+++ b/examples/6_Linked-widgets.ipynb
@@ -31,8 +31,8 @@
     "widgets.jslink((b, \"shared_target\"), (c, \"shared_target\"))\n",
     "\n",
     "# synchronize FoV (zoom level) between 3 widgets\n",
-    "widgets.jslink((a, \"fov\"), (b, \"fov\"))\n",
-    "widgets.jslink((b, \"fov\"), (c, \"fov\"))\n",
+    "widgets.jslink((a, \"shared_fov\"), (b, \"shared_fov\"))\n",
+    "widgets.jslink((b, \"shared_fov\"), (c, \"shared_fov\"))\n",
     "\n",
     "items = [a, b, c]\n",
     "\n",

From 7480bd949e5d3545dfa9bef86c1f74b71409266c Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Thu, 25 Apr 2024 10:27:38 +0200
Subject: [PATCH 04/11] :memo: Add documentation for _fov and shared_fov traits

---
 src/ipyaladin/__init__.py | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/ipyaladin/__init__.py b/src/ipyaladin/__init__.py
index beec5611..e5ec002d 100644
--- a/src/ipyaladin/__init__.py
+++ b/src/ipyaladin/__init__.py
@@ -43,8 +43,17 @@ class Aladin(anywidget.AnyWidget):
         help="A trait that can be used with `~ipywidgets.widgets.jslink`"
         "to link two Aladin Lite widgets targets together",
     ).tag(sync=True)
-    _fov = Float(60.0).tag(sync=True, init_option=True)
-    shared_fov = Float(60.0).tag(sync=True)
+    _fov = Float(
+        60.0,
+        help="A private trait that stores the current field of view of the widget."
+        " Its public version is the 'fov' property that returns an "
+        "`~astropy.units.Angle` object",
+    ).tag(sync=True, init_option=True)
+    shared_fov = Float(
+        60.0,
+        help="A trait that can be used with `~ipywidgets.widgets.jslink`"
+        "to link two Aladin Lite widgets field of view together",
+    ).tag(sync=True)
     survey = Unicode("https://alaskybis.unistra.fr/DSS/DSSColor").tag(
         sync=True, init_option=True
     )

From 08e8b7b5581b9fe7e3c302112742ba7840e1c8b5 Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Thu, 25 Apr 2024 10:40:19 +0200
Subject: [PATCH 05/11] :fire: Remove unnecessary Aladin#setFoV method call

---
 js/widget.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/js/widget.js b/js/widget.js
index 093ba153..3d997108 100644
--- a/js/widget.js
+++ b/js/widget.js
@@ -37,7 +37,6 @@ function render({ model, el }) {
 
   const ra_dec = init_options["target"].split(" ");
   aladin.gotoRaDec(ra_dec[0], ra_dec[1]);
-  aladin.setFoV(init_options["fov"]);
 
   el.appendChild(aladinDiv);
 

From 69be36243817f8f44148904580f0e0173d923f6a Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Thu, 25 Apr 2024 12:32:23 +0200
Subject: [PATCH 06/11] :memo: Fix example 10

---
 examples/10_Advanced-GUI.ipynb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/examples/10_Advanced-GUI.ipynb b/examples/10_Advanced-GUI.ipynb
index e28d939c..dfdab0bc 100644
--- a/examples/10_Advanced-GUI.ipynb
+++ b/examples/10_Advanced-GUI.ipynb
@@ -92,7 +92,7 @@
     "\n",
     "\n",
     "zoom_slider = widgets.FloatSlider(\n",
-    "    value=180 / aladin.fov,\n",
+    "    value=180 / aladin.fov.deg,\n",
     "    min=1,\n",
     "    max=400,\n",
     "    step=1,\n",

From d5d692466accebe8a76602654ae76d5765fbedd3 Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Thu, 25 Apr 2024 14:08:56 +0200
Subject: [PATCH 07/11] :memo: Fix example 7

---
 examples/7_on-click-callback.ipynb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/examples/7_on-click-callback.ipynb b/examples/7_on-click-callback.ipynb
index 128f748e..90b1af0e 100644
--- a/examples/7_on-click-callback.ipynb
+++ b/examples/7_on-click-callback.ipynb
@@ -49,7 +49,7 @@
     "        dec,\n",
     "        ra,\n",
     "        dec,\n",
-    "        aladin.fov / 10,\n",
+    "        aladin.fov.deg / 10,\n",
     "    )\n",
     "\n",
     "    r = requests.get(\n",

From d73d8ec726ed56092bb5902f8b7e6b78f9a5824d Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Thu, 25 Apr 2024 15:55:21 +0200
Subject: [PATCH 08/11] :memo: Update changelog and fix typo

---
 CHANGELOG.md | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd380bca..ddb56d74 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 - Support for `astropy.coordinates.SkyCoord` for assigning and reading the `target` property (#80)
+- Support for `astropy.coordinates.Angle` for reading the `fov` property (#83)
 
 ### Fixed
 
@@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 - Change the jslink target trait from `target` to `shared_target` (#80)
+- Change the jslink fov trait from `fov` to `shared_fov` (#83)
 
 ## [0.3.0]
 
@@ -37,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   the last clicked object (ra, dec, and catalog content)
 - Each ipyaladin version now point to a specific Aladin-lite version instead of the latest available version
 - add_table takes new arguments (documented here https://cds-astro.github.io/aladin-lite/Catalog.Catalog.html)
-- the new method `add_moc` can take mocpy.MOC objects, URLs, or the dictionnary serialization of a MOC. This will replace `moc_from_URL` and `moc_from_dict` in the future.
+- the new method `add_moc` can take mocpy.MOC objects, URLs, or the dictionary serialization of a MOC. This will replace `moc_from_URL` and `moc_from_dict` in the future.
 
 ## [0.2.6]
 

From 42c93cec2d66fd284cf348892978776cfefe8e8b Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Thu, 25 Apr 2024 15:43:12 +0200
Subject: [PATCH 09/11] :white_check_mark: Add integration testing cases for
 Angle fov

---
 src/test/test_aladin.py            | 107 +++++++++++++++++++++++++++++
 src/test/test_coordinate_parser.py |  65 ------------------
 2 files changed, 107 insertions(+), 65 deletions(-)
 create mode 100644 src/test/test_aladin.py

diff --git a/src/test/test_aladin.py b/src/test/test_aladin.py
new file mode 100644
index 00000000..e7eba792
--- /dev/null
+++ b/src/test/test_aladin.py
@@ -0,0 +1,107 @@
+import pytest
+from astropy.coordinates import Angle
+
+from ipyaladin import Aladin, parse_coordinate_string
+
+test_aladin_string_target = [
+    "M 31",
+    "sgr a*",
+    "α Centauri",  # noqa RUF001
+    "* 17 Com",
+    "1:12:43.2 31:12:43",
+    "1:12:43.2 +31:12:43",
+    "1:12:43.2 -31:12:43",
+    "1 12 43.2 31 12 43",
+    "1 12 43.2 +31 12 43",
+    "1 12 43.2 -31 12 43",
+    "1h12m43.2s 1d12m43s",
+    "1h12m43.2s +1d12m43s",
+    "1h12m43.2s -1d12m43s",
+    "42.67 25.48",
+    "42.67 +25.48",
+    "42.67 -25.48",
+    "0 0",
+    "J42.67 25.48",
+    "G42.67 25.48",
+    "B42.67 25.48",
+    "J12 30 45 +45 30 15",
+    "J03 15 20 -10 20 30",
+    "G120.5 -45.7",
+    "G90 0",
+    "B60 30",
+    "B120 -45",
+]
+
+
+@pytest.mark.parametrize("target", test_aladin_string_target)
+def test_aladin_string_target_set(target):
+    """Test setting the target of an Aladin object with a string or a SkyCoord object.
+
+    Parameters
+    ----------
+    target : str
+        The target string.
+
+    """
+    aladin = Aladin()
+    aladin.target = target
+    parsed_target = parse_coordinate_string(target)
+    assert aladin.target.icrs.ra.deg == parsed_target.icrs.ra.deg
+    assert aladin.target.icrs.dec.deg == parsed_target.icrs.dec.deg
+
+
+@pytest.mark.parametrize("target", test_aladin_string_target)
+def test_aladin_sky_coord_target_set(target):
+    """Test setting and getting the target of an Aladin object with a SkyCoord object.
+
+    Parameters
+    ----------
+    target : str
+        The target string.
+
+    """
+    sc_target = parse_coordinate_string(target)
+    aladin = Aladin()
+    aladin.target = sc_target
+    assert aladin.target.icrs.ra.deg == sc_target.icrs.ra.deg
+    assert aladin.target.icrs.dec.deg == sc_target.icrs.dec.deg
+
+
+test_aladin_float_fov = [
+    0,
+    360,
+    180,
+    -180,
+    720,
+]
+
+
+@pytest.mark.parametrize("angle", test_aladin_float_fov)
+def test_aladin_float_fov_set(angle):
+    """Test setting the angle of an Aladin object with a float.
+
+    Parameters
+    ----------
+    angle : float
+        The angle to set.
+
+    """
+    aladin = Aladin()
+    aladin.fov = angle
+    assert aladin.fov.deg == angle
+
+
+@pytest.mark.parametrize("angle", test_aladin_float_fov)
+def test_aladin_angle_fov_set(angle):
+    """Test setting the angle of an Aladin object with an Angle object.
+
+    Parameters
+    ----------
+    angle : float
+        The angle to set.
+
+    """
+    angle_fov = Angle(angle, unit="deg")
+    aladin = Aladin()
+    aladin.fov = angle_fov
+    assert aladin.fov.deg == angle_fov.deg
diff --git a/src/test/test_coordinate_parser.py b/src/test/test_coordinate_parser.py
index 6d4f78f1..e0cd3ff6 100644
--- a/src/test/test_coordinate_parser.py
+++ b/src/test/test_coordinate_parser.py
@@ -1,4 +1,3 @@
-from ipyaladin import Aladin
 from ipyaladin.coordinate_parser import (
     parse_coordinate_string,
     _split_coordinate_string,
@@ -188,67 +187,3 @@ def test_parse_coordinate_string(inp, expected):
 
     """
     assert parse_coordinate_string(inp) == expected
-
-
-test_aladin_string_target = [
-    "M 31",
-    "sgr a*",
-    "α Centauri",  # noqa RUF001
-    "* 17 Com",
-    "1:12:43.2 31:12:43",
-    "1:12:43.2 +31:12:43",
-    "1:12:43.2 -31:12:43",
-    "1 12 43.2 31 12 43",
-    "1 12 43.2 +31 12 43",
-    "1 12 43.2 -31 12 43",
-    "1h12m43.2s 1d12m43s",
-    "1h12m43.2s +1d12m43s",
-    "1h12m43.2s -1d12m43s",
-    "42.67 25.48",
-    "42.67 +25.48",
-    "42.67 -25.48",
-    "0 0",
-    "J42.67 25.48",
-    "G42.67 25.48",
-    "B42.67 25.48",
-    "J12 30 45 +45 30 15",
-    "J03 15 20 -10 20 30",
-    "G120.5 -45.7",
-    "G90 0",
-    "B60 30",
-    "B120 -45",
-]
-
-
-@pytest.mark.parametrize("target", test_aladin_string_target)
-def test_aladin_string_target_set(target):
-    """Test setting the target of an Aladin object with a string or a SkyCoord object.
-
-    Parameters
-    ----------
-    target : str
-        The target string.
-
-    """
-    aladin = Aladin()
-    aladin.target = target
-    parsed_target = parse_coordinate_string(target)
-    assert aladin.target.icrs.ra.deg == parsed_target.icrs.ra.deg
-    assert aladin.target.icrs.dec.deg == parsed_target.icrs.dec.deg
-
-
-@pytest.mark.parametrize("target", test_aladin_string_target)
-def test_aladin_sky_coord_target_set(target):
-    """Test setting and getting the target of an Aladin object with a SkyCoord object.
-
-    Parameters
-    ----------
-    target : str
-        The target string.
-
-    """
-    sc_target = parse_coordinate_string(target)
-    aladin = Aladin()
-    aladin.target = sc_target
-    assert aladin.target.icrs.ra.deg == sc_target.icrs.ra.deg
-    assert aladin.target.icrs.dec.deg == sc_target.icrs.dec.deg

From 8433cc9389b05729f3d2841d18f398ceddabd435 Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Mon, 29 Apr 2024 12:44:18 +0200
Subject: [PATCH 10/11] :memo: Improve documentation clarity

---
 src/ipyaladin/__init__.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/ipyaladin/__init__.py b/src/ipyaladin/__init__.py
index e5ec002d..96a1133b 100644
--- a/src/ipyaladin/__init__.py
+++ b/src/ipyaladin/__init__.py
@@ -132,9 +132,10 @@ def _handle_custom_message(self, model, message, list_of_buffers):  # noqa: ARG0
 
     @property
     def fov(self) -> Angle:
-        """The field of view of the Aladin Lite widget.
+        """The field of view of the Aladin Lite widget along the horizontal axis.
 
-        It can be set with either a float or an `~astropy.units.Angle` object.
+        It can be set with either a float in degrees
+        or an `~astropy.units.Angle` object.
 
         Returns
         -------

From 6c53198102f88178eb7b6f8815fd9ccb7cae2c18 Mon Sep 17 00:00:00 2001
From: Xen0Xys <red57101@gmail.com>
Date: Mon, 29 Apr 2024 14:24:27 +0200
Subject: [PATCH 11/11] :memo: Update aladin correspondence table

---
 README.md | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index c310e552..0dd1bfd4 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,7 @@ Ipyaladin brings [Aladin-lite](https://github.com/cds-astro/aladin-lite) into no
 
 Correspondence table between ipyaladin versions and Aladin-lite versions:
 
-| ipyaladin | Aladin-Lite |
-| --------- | ----------- |
-| 0.3.0     | 3.3.3-dev   |
+| ipyaladin  | Aladin-Lite |
+| ---------- | ----------- |
+| 0.3.0      | 3.3.3-dev   |
+| unreleased | 3.4.0-beta  |