Pārlūkot izejas kodu

Merge branch 'main' of github.com:zauberzeug/nicegui

Rodja Trappe 4 mēneši atpakaļ
vecāks
revīzija
c3ce92a396

+ 21 - 0
examples/custom_vue_component/README.md

@@ -0,0 +1,21 @@
+# Custom Vue Component
+
+This example demonstrates how to create and use custom Vue components in NiceGUI.
+One component is implemented using JavaScript, the other using Vue's Single-File Component (SFC) syntax.
+
+## Counter Component (counter.js)
+
+The `Counter` component is a simple counter that increments a value.
+On change, it emits an event with the new value.
+A reset method allows to reset the counter to 0.
+
+The JavaScript code in `counter.js` defines the front-end logic of the component using JavaScript.
+
+## OnOff Component (on_off.vue)
+
+The `OnOff` component is a simple toggle that switches between on and off.
+On change, it emits an event with the new value.
+A reset method is also provided to reset the toggle to off.
+
+The Single-File Component in `on_off.vue` defines the front-end logic of the component using Vue 2.
+In contrast to the JavaScript code in `counter.js`, it splits the template, script, and style into separate sections.

+ 7 - 6
examples/custom_vue_component/counter.js

@@ -1,9 +1,13 @@
 // NOTE: Make sure to reload the browser with cache disabled after making changes to this file.
 export default {
   template: `
-  <button @click="handle_click">
-    <strong>{{title}}: {{value}}</strong>
-  </button>`,
+    <button @click="handle_click" :style="{ background: value > 0 ? '#bf8' : '#eee', padding: '8px 16px', borderRadius: '4px' }">
+      <strong>{{title}}: {{value}}</strong>
+    </button>
+  `,
+  props: {
+    title: String,
+  },
   data() {
     return {
       value: 0,
@@ -18,7 +22,4 @@ export default {
       this.value = 0;
     },
   },
-  props: {
-    title: String,
-  },
 };

+ 8 - 9
examples/custom_vue_component/main.py

@@ -1,17 +1,16 @@
 #!/usr/bin/env python3
 from counter import Counter
+from on_off import OnOff
 
 from nicegui import ui
 
-ui.markdown('''
-#### Try the new click counter!
+with ui.row(align_items='center'):
+    counter = Counter('Count', on_change=lambda e: ui.notify(f'The value changed to {e.args}.'))
+    ui.button('Reset', on_click=counter.reset).props('outline')
 
-Click to increment its value.
-''')
-with ui.card():
-    counter = Counter('Clicks', on_change=lambda e: ui.notify(f'The value changed to {e.args}.'))
+with ui.row(align_items='center'):
+    on_off = OnOff('State', on_change=lambda e: ui.notify(f'The value changed to {e.args}.'))
+    ui.button('Reset', on_click=on_off.reset).props('outline')
 
 
-ui.button('Reset', on_click=counter.reset).props('small outline')
-
-ui.run()
+ui.run(uvicorn_reload_includes='*.py,*.js,*.vue')

+ 14 - 0
examples/custom_vue_component/on_off.py

@@ -0,0 +1,14 @@
+from typing import Callable, Optional
+
+from nicegui.element import Element
+
+
+class OnOff(Element, component='on_off.vue'):
+
+    def __init__(self, title: str, *, on_change: Optional[Callable] = None) -> None:
+        super().__init__()
+        self._props['title'] = title
+        self.on('change', on_change)
+
+    def reset(self) -> None:
+        self.run_method('reset')

+ 41 - 0
examples/custom_vue_component/on_off.vue

@@ -0,0 +1,41 @@
+<template>
+  <div>
+    <button @click="handle_click" :class="{ active: value }">
+      <strong>{{ title }}: {{ value ? "ON" : "OFF" }}</strong>
+    </button>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    title: String,
+  },
+  data() {
+    return {
+      value: false,
+    };
+  },
+  methods: {
+    handle_click() {
+      this.value = !this.value;
+      this.$emit("change", this.value);
+    },
+    reset() {
+      this.value = false;
+    },
+  },
+};
+</script>
+
+<style scoped>
+button {
+  background-color: #eee;
+  padding: 8px 16px;
+  border-radius: 4px;
+}
+
+button.active {
+  background-color: #fb8;
+}
+</style>

+ 5 - 0
nicegui/elements/keyboard.js

@@ -2,10 +2,15 @@ export default {
   mounted() {
     for (const event of this.events) {
       document.addEventListener(event, (evt) => {
+        // https://github.com/zauberzeug/nicegui/issues/4290
+        if (!(evt instanceof KeyboardEvent)) return;
+
         // https://stackoverflow.com/a/36469636/3419103
         const focus = document.activeElement;
         if (focus && this.ignore.includes(focus.tagName.toLowerCase())) return;
+
         if (evt.repeat && !this.repeating) return;
+
         this.$emit("key", {
           action: event,
           altKey: evt.altKey,

+ 2 - 0
nicegui/elements/leaflet.js

@@ -10,12 +10,14 @@ export default {
     draw_control: Object,
     resource_path: String,
     hide_drawn_items: Boolean,
+    additional_resources: Array,
   },
   async mounted() {
     await this.$nextTick(); // NOTE: wait for window.path_prefix to be set
     await Promise.all([
       loadResource(window.path_prefix + `${this.resource_path}/leaflet/leaflet.css`),
       loadResource(window.path_prefix + `${this.resource_path}/leaflet/leaflet.js`),
+      ...this.additional_resources.map((resource) => loadResource(resource)),
     ]);
     if (this.draw_control) {
       await Promise.all([

+ 4 - 1
nicegui/elements/leaflet.py

@@ -1,6 +1,6 @@
 import asyncio
 from pathlib import Path
-from typing import Any, Dict, List, Tuple, Union, cast
+from typing import Any, Dict, List, Optional, Tuple, Union, cast
 
 from typing_extensions import Self
 
@@ -27,6 +27,7 @@ class Leaflet(Element, component='leaflet.js', default_classes='nicegui-leaflet'
                  options: Dict = {},  # noqa: B006
                  draw_control: Union[bool, Dict] = False,
                  hide_drawn_items: bool = False,
+                 additional_resources: Optional[List[str]] = None,
                  ) -> None:
         """Leaflet map
 
@@ -37,6 +38,7 @@ class Leaflet(Element, component='leaflet.js', default_classes='nicegui-leaflet'
         :param draw_control: whether to show the draw toolbar (default: False)
         :param options: additional options passed to the Leaflet map (default: {})
         :param hide_drawn_items: whether to hide drawn items on the map (default: False, *added in version 2.0.0*)
+        :param additional_resources: additional resources like CSS or JS files to load (default: None)
         """
         super().__init__()
         self.add_resource(Path(__file__).parent / 'lib' / 'leaflet')
@@ -51,6 +53,7 @@ class Leaflet(Element, component='leaflet.js', default_classes='nicegui-leaflet'
         self._props['options'] = {**options}
         self._props['draw_control'] = draw_control
         self._props['hide_drawn_items'] = hide_drawn_items
+        self._props['additional_resources'] = additional_resources or []
 
         self.on('init', self._handle_init)
         self.on('map-moveend', self._handle_moveend)

+ 3 - 3
poetry.lock

@@ -281,13 +281,13 @@ files = [
 
 [[package]]
 name = "certifi"
-version = "2024.12.14"
+version = "2025.1.31"
 description = "Python package for providing Mozilla's CA Bundle."
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
-    {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
+    {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
+    {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
 ]
 
 [[package]]

+ 14 - 0
website/documentation/content/leaflet_documentation.py

@@ -196,4 +196,18 @@ async def wait_for_init() -> None:
         m.run_map_method('fitBounds', [[bounds['_southWest'], bounds['_northEast']]])
     ui.timer(0, page, once=True)  # HIDE
 
+
+@doc.demo('Leaflet Plugins', '''
+    You can add plugins to the map by passing the URLs of JS and CSS files to the `additional_resources` parameter.
+    This demo shows how to add the [Leaflet.RotatedMarker](https://github.com/bbecquet/Leaflet.RotatedMarker) plugin.
+    It allows you to rotate markers by a given `rotationAngle`.
+''')
+def leaflet_plugins() -> None:
+    m = ui.leaflet((51.51, -0.09), additional_resources=[
+        'https://unpkg.com/leaflet-rotatedmarker@0.2.0/leaflet.rotatedMarker.js',
+    ])
+    m.marker(latlng=(51.51, -0.091), options={'rotationAngle': -30})
+    m.marker(latlng=(51.51, -0.090), options={'rotationAngle': 30})
+
+
 doc.reference(ui.leaflet)