Repository: mwouts/itables

Issue:
Crash when parsing style string (regression from v2.3)
I am using `show` for to render a table and get an error (see below)

This is what doesn't work with itables v2.4, which used to work in v2.3:

```python
    show(
        styler,
        buttons=["copyHtml5", "csvHtml5"],
        style='width: fit-content; float: left;',
        allow_html=True,
    )
```

The styler is a styled pandas table.

Removing the final `;` in the style string works. According to the CSS standard this semicolon is not mandatory, but allowed.


The error:

```
    342 display_logo_when_loading = dt_args.pop("display_logo_when_loading", False)
    344 check_itable_arguments(cast(dict[str, Any], dt_args), DTForITablesOptions)
--> 345 return html_table_from_template(
    346     table_id=table_id,
    347     dt_url=dt_url,
    348     connected=connected,
    349     display_logo_when_loading=display_logo_when_loading,
    350     kwargs=dt_args,
    351 )

File ~/devel/pharmpy/.tox/py313/lib/python3.13/site-packages/itables/javascript.py:727, in html_table_from_template(table_id, dt_url, connected, display_logo_when_loading, kwargs)
    725 kwargs["classes"] = get_expanded_classes(kwargs["classes"])
    726 assert "style" in kwargs
--> 727 kwargs["style"] = get_expanded_style(kwargs["style"])
    729 # Export the DT args to JSON
    730 dt_args = json.dumps(kwargs)

File ~/devel/pharmpy/.tox/py313/lib/python3.13/site-packages/itables/javascript.py:120, in get_expanded_style(style)
    116     return style
    117 elif isinstance(style, str):
    118     return {
    119         k.strip(): v.strip()
--> 120         for k, v in (item.split(":") for item in style.split(";"))
    121     }
    122 else:
    123     raise TypeError(f"style must be a string or a dict, not {type(style)}")

ValueError: not enough values to unpack (expected 2, got 1)
```

Discussion hints:
github-actions[bot]: Thank you for making this pull request.

Did you know? You can try it on Binder: [![Binder:lab](https://img.shields.io/badge/binder-jupyterlab-0172B2.svg)](https://mybinder.org/v2/gh/mwouts/itables/fix_386?urlpath=lab/tree/docs/quick_start.md).

Also, the version of ITables developed in this PR can be installed with `pip`:
```
pip install git+https://github.com/mwouts/itables.git@fix_386
```
(this requires `nodejs`, see more at [Developing ITables](https://mwouts.github.io/itables/developing.html))
<!-- thollander/actions-comment-pull-request "binder_link" -->
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/mwouts/itables/pull/391?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marc+Wouts) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 88.89%. Comparing base [(`6da378b`)](https://app.codecov.io/gh/mwouts/itables/commit/6da378b8d567247ae0e7ab7a735dd40c3ec173c8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marc+Wouts) to head [(`2d157aa`)](https://app.codecov.io/gh/mwouts/itables/commit/2d157aa13b3d0900e2f602e4e61dc473fda20f2b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marc+Wouts).

:x: Your project status has failed because the head coverage (91.29%) is below the target coverage (93.00%). You can increase the head coverage or adjust the [target](https://docs.codecov.com/docs/commit-status#target) coverage.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #391      +/-   ##
==========================================
- Coverage   88.91%   88.89%   -0.03%     
==========================================
  Files          44       44              
  Lines        1832     1837       +5     
==========================================
+ Hits         1629     1633       +4     
- Misses        203      204       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/mwouts/itables/pull/391?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marc+Wouts).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Marc+Wouts).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>

Solution patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index a645113..c5bfb7e 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -13,6 +13,7 @@ ITables ChangeLog
 
 **Fixed**
 - We have added type hints to `itable.options` even for the options that don't have a default value ([#224](https://github.com/mwouts/itables/issues/224))
+- The optional final semicolon in `style` argument is now supported again ([#386](https://github.com/mwouts/itables/issues/386))
 
 
 2.4.0 (2025-05-17)
diff --git a/src/itables/javascript.py b/src/itables/javascript.py
index 7c743c6..82d6f65 100644
--- a/src/itables/javascript.py
+++ b/src/itables/javascript.py
@@ -117,7 +117,7 @@ def get_expanded_style(style: Union[str, Mapping[str, str]]) -> dict[str, str]:
     elif isinstance(style, str):
         return {
             k.strip(): v.strip()
-            for k, v in (item.split(":") for item in style.split(";"))
+            for k, v in (item.split(":") for item in style.split(";") if item.strip())
         }
     else:
         raise TypeError(f"style must be a string or a dict, not {type(style)}")


Test patch:
diff --git a/tests/test_javascript.py b/tests/test_javascript.py
index 1af283b..b93165f 100644
--- a/tests/test_javascript.py
+++ b/tests/test_javascript.py
@@ -12,6 +12,7 @@ from itables.javascript import (
     check_table_id,
     get_compact_classes,
     get_compact_style,
+    get_expanded_style,
     get_itable_arguments,
     replace_value,
     to_html_datatable,
@@ -153,3 +154,16 @@ def test_unpkg_urls_are_up_to_date():
         UNPKG_DT_BUNDLE_CSS
         == f"https://www.unpkg.com/dt_for_itables@{bundle_version}/dt_bundle.css"
     )
+
+
+@pytest.mark.parametrize(
+    "style",
+    [
+        "width: fit-content; float: left;",
+        "width: fit-content; float: left; overflow: auto; max-height: 500px",
+    ],
+)
+def test_get_expanded_style(style: str):
+    exp_style = get_expanded_style(style)
+    compact_style = get_compact_style(exp_style)
+    assert compact_style == style.replace(" ", "").removesuffix(";")