Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/html/component/
scope.rs

1//! Component scope module
2
3use std::any::{Any, TypeId};
4use std::marker::PhantomData;
5use std::ops::Deref;
6use std::rc::Rc;
7use std::{fmt, iter};
8
9use futures::{Stream, StreamExt};
10
11use super::BaseComponent;
12#[cfg(any(feature = "csr", feature = "ssr"))]
13use super::lifecycle::ComponentState;
14use crate::callback::Callback;
15use crate::context::{ContextHandle, ContextProvider};
16use crate::platform::spawn_local;
17#[cfg(any(feature = "csr", feature = "ssr"))]
18use crate::scheduler::Shared;
19
20/// Untyped scope used for accessing parent scope
21#[derive(Clone)]
22pub struct AnyScope {
23    type_id: TypeId,
24    parent: Option<Rc<AnyScope>>,
25    typed_scope: Rc<dyn Any>,
26}
27
28impl fmt::Debug for AnyScope {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        f.debug_struct("AnyScope").finish_non_exhaustive()
31    }
32}
33
34impl<COMP: BaseComponent> From<Scope<COMP>> for AnyScope {
35    fn from(scope: Scope<COMP>) -> Self {
36        AnyScope {
37            type_id: TypeId::of::<COMP>(),
38            parent: scope.parent.clone(),
39            typed_scope: Rc::new(scope),
40        }
41    }
42}
43
44impl AnyScope {
45    /// Returns the parent scope
46    pub fn get_parent(&self) -> Option<&AnyScope> {
47        self.parent.as_deref()
48    }
49
50    /// Returns the type of the linked component
51    pub fn get_type_id(&self) -> &TypeId {
52        &self.type_id
53    }
54
55    /// Attempts to downcast into a typed scope
56    ///
57    /// # Panics
58    ///
59    /// If the self value can't be cast into the target type.
60    pub fn downcast<COMP: BaseComponent>(&self) -> Scope<COMP> {
61        self.try_downcast::<COMP>().unwrap()
62    }
63
64    /// Attempts to downcast into a typed scope
65    ///
66    /// Returns [`None`] if the self value can't be cast into the target type.
67    pub fn try_downcast<COMP: BaseComponent>(&self) -> Option<Scope<COMP>> {
68        self.typed_scope.downcast_ref::<Scope<COMP>>().cloned()
69    }
70
71    /// Attempts to find a parent scope of a certain type
72    ///
73    /// Returns [`None`] if no parent scope with the specified type was found.
74    pub fn find_parent_scope<COMP: BaseComponent>(&self) -> Option<Scope<COMP>> {
75        iter::successors(Some(self), |scope| scope.get_parent())
76            .find_map(AnyScope::try_downcast::<COMP>)
77    }
78
79    /// Accesses a value provided by a parent `ContextProvider` component of the
80    /// same type.
81    pub fn context<T: Clone + PartialEq + 'static>(
82        &self,
83        callback: Callback<T>,
84    ) -> Option<(T, ContextHandle<T>)> {
85        let scope = self.find_parent_scope::<ContextProvider<T>>()?;
86        let scope_clone = scope.clone();
87        let component = scope.get_component()?;
88        Some(component.subscribe_consumer(callback, scope_clone))
89    }
90}
91
92/// A context which allows sending messages to a component.
93pub struct Scope<COMP: BaseComponent> {
94    _marker: PhantomData<COMP>,
95    parent: Option<Rc<AnyScope>>,
96
97    #[cfg(any(feature = "csr", feature = "ssr"))]
98    pub(crate) pending_messages: MsgQueue<COMP::Message>,
99
100    #[cfg(any(feature = "csr", feature = "ssr"))]
101    pub(crate) state: Shared<Option<ComponentState>>,
102
103    pub(crate) id: usize,
104}
105
106impl<COMP: BaseComponent> fmt::Debug for Scope<COMP> {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        f.debug_struct("Scope<_>").finish_non_exhaustive()
109    }
110}
111
112impl<COMP: BaseComponent> Clone for Scope<COMP> {
113    fn clone(&self) -> Self {
114        Scope {
115            _marker: PhantomData,
116
117            #[cfg(any(feature = "csr", feature = "ssr"))]
118            pending_messages: self.pending_messages.clone(),
119            parent: self.parent.clone(),
120
121            #[cfg(any(feature = "csr", feature = "ssr"))]
122            state: self.state.clone(),
123
124            id: self.id,
125        }
126    }
127}
128
129impl<COMP: BaseComponent> Scope<COMP> {
130    /// Returns the parent scope
131    pub fn get_parent(&self) -> Option<&AnyScope> {
132        self.parent.as_deref()
133    }
134
135    /// Creates a `Callback` which will send a message to the linked
136    /// component's update method when invoked.
137    ///
138    /// If your callback function returns a [Future],
139    /// use [`callback_future`](Scope::callback_future) instead.
140    pub fn callback<F, IN, M>(&self, function: F) -> Callback<IN>
141    where
142        M: Into<COMP::Message>,
143        F: Fn(IN) -> M + 'static,
144    {
145        let scope = self.clone();
146        let closure = move |input| {
147            let output = function(input);
148            scope.send_message(output);
149        };
150        Callback::from(closure)
151    }
152
153    /// Creates a `Callback` which will send a batch of messages back
154    /// to the linked component's update method when invoked.
155    ///
156    /// The callback function's return type is generic to allow for dealing with both
157    /// `Option` and `Vec` nicely. `Option` can be used when dealing with a callback that
158    /// might not need to send an update.
159    ///
160    /// ```ignore
161    /// link.batch_callback(|_| vec![Msg::A, Msg::B]);
162    /// link.batch_callback(|_| Some(Msg::A));
163    /// ```
164    pub fn batch_callback<F, IN, OUT>(&self, function: F) -> Callback<IN>
165    where
166        F: Fn(IN) -> OUT + 'static,
167        OUT: SendAsMessage<COMP>,
168    {
169        let scope = self.clone();
170        let closure = move |input| {
171            let messages = function(input);
172            messages.send(&scope);
173        };
174        closure.into()
175    }
176
177    /// Accesses a value provided by a parent `ContextProvider` component of the
178    /// same type.
179    pub fn context<T: Clone + PartialEq + 'static>(
180        &self,
181        callback: Callback<T>,
182    ) -> Option<(T, ContextHandle<T>)> {
183        AnyScope::from(self.clone()).context(callback)
184    }
185
186    /// This method asynchronously awaits a [Future] that returns a message and sends it
187    /// to the linked component.
188    ///
189    /// # Panics
190    /// If the future panics, then the promise will not resolve, and will leak.
191    pub fn send_future<Fut, Msg>(&self, future: Fut)
192    where
193        Msg: Into<COMP::Message>,
194        Fut: Future<Output = Msg> + 'static,
195    {
196        let link = self.clone();
197        spawn_local(async move {
198            let message: COMP::Message = future.await.into();
199            link.send_message(message);
200        });
201    }
202
203    /// This method creates a [`Callback`] which, when emitted, asynchronously awaits the
204    /// message returned from the passed function before sending it to the linked component.
205    ///
206    /// # Panics
207    /// If the future panics, then the promise will not resolve, and will leak.
208    pub fn callback_future<F, Fut, IN, Msg>(&self, function: F) -> Callback<IN>
209    where
210        Msg: Into<COMP::Message>,
211        Fut: Future<Output = Msg> + 'static,
212        F: Fn(IN) -> Fut + 'static,
213    {
214        let link = self.clone();
215
216        let closure = move |input: IN| {
217            link.send_future(function(input));
218        };
219
220        closure.into()
221    }
222
223    /// Asynchronously send a batch of messages to a component. This asynchronously awaits the
224    /// passed [Future], before sending the message batch to the linked component.
225    ///
226    /// # Panics
227    /// If the future panics, then the promise will not resolve, and will leak.
228    pub fn send_future_batch<Fut>(&self, future: Fut)
229    where
230        Fut: Future<Output: SendAsMessage<COMP>> + 'static,
231    {
232        let link = self.clone();
233        let js_future = async move {
234            future.await.send(&link);
235        };
236        spawn_local(js_future);
237    }
238
239    /// This method asynchronously awaits a [`Stream`] that returns a series of messages and sends
240    /// them to the linked component.
241    ///
242    /// # Panics
243    /// If the stream panics, then the promise will not resolve, and will leak.
244    ///
245    /// # Note
246    ///
247    /// This method will not notify the component when the stream has been fully exhausted. If
248    /// you want this feature, you can add an EOF message variant for your component and use
249    /// [`StreamExt::chain`] and [`stream::once`](futures::stream::once) to chain an EOF message to
250    /// the original stream. If your stream is produced by another crate, you can use
251    /// [`StreamExt::map`] to transform the stream's item type to the component message type.
252    pub fn send_stream<S, M>(&self, stream: S)
253    where
254        M: Into<COMP::Message>,
255        S: Stream<Item = M> + 'static,
256    {
257        let link = self.clone();
258        let js_future = async move {
259            futures::pin_mut!(stream);
260            while let Some(msg) = stream.next().await {
261                let message: COMP::Message = msg.into();
262                link.send_message(message);
263            }
264        };
265        spawn_local(js_future);
266    }
267
268    /// Returns the linked component if available
269    pub fn get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
270        self.arch_get_component()
271    }
272
273    /// Send a message to the component.
274    pub fn send_message<T>(&self, msg: T)
275    where
276        T: Into<COMP::Message>,
277    {
278        self.arch_send_message(msg)
279    }
280
281    /// Send a batch of messages to the component.
282    ///
283    /// This is slightly more efficient than calling [`send_message`](Self::send_message)
284    /// in a loop.
285    pub fn send_message_batch(&self, messages: Vec<COMP::Message>) {
286        self.arch_send_message_batch(messages)
287    }
288}
289
290#[cfg(feature = "ssr")]
291mod feat_ssr {
292    use std::fmt::Write;
293
294    use super::*;
295    use crate::feat_ssr::VTagKind;
296    use crate::html::component::lifecycle::{
297        ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner,
298    };
299    use crate::platform::fmt::BufWriter;
300    use crate::platform::pinned::oneshot;
301    use crate::scheduler;
302    use crate::virtual_dom::Collectable;
303
304    impl<COMP: BaseComponent> Scope<COMP> {
305        pub(crate) async fn render_into_stream(
306            &self,
307            w: &mut BufWriter,
308            props: Rc<COMP::Properties>,
309            hydratable: bool,
310            parent_vtag_kind: VTagKind,
311        ) {
312            // Rust's Future implementation is stack-allocated and incurs zero runtime-cost.
313            //
314            // If the content of this channel is ready before it is awaited, it is
315            // similar to taking the value from a mutex lock.
316            let (tx, rx) = oneshot::channel();
317            let state = ComponentRenderState::Ssr { sender: Some(tx) };
318
319            scheduler::push_component_create(
320                self.id,
321                Box::new(CreateRunner {
322                    initial_render_state: state,
323                    props,
324                    scope: self.clone(),
325                    #[cfg(feature = "hydration")]
326                    prepared_state: None,
327                }),
328                Box::new(RenderRunner {
329                    state: self.state.clone(),
330                }),
331            );
332            scheduler::start();
333
334            let collectable = Collectable::for_component::<COMP>();
335
336            if hydratable {
337                collectable.write_open_tag(w);
338            }
339
340            let html = rx.await.unwrap();
341
342            let self_any_scope = AnyScope::from(self.clone());
343            html.render_into_stream(w, &self_any_scope, hydratable, parent_vtag_kind)
344                .await;
345
346            if let Some(prepared_state) = self.get_component().unwrap().prepare_state() {
347                let _ = w.write_str(r#"<script type="application/x-yew-comp-state">"#);
348                let _ = w.write_str(&prepared_state);
349                let _ = w.write_str(r#"</script>"#);
350            }
351
352            if hydratable {
353                collectable.write_close_tag(w);
354            }
355
356            scheduler::push_component_destroy(Box::new(DestroyRunner {
357                state: self.state.clone(),
358                parent_to_detach: false,
359            }));
360            scheduler::start();
361        }
362    }
363}
364
365#[cfg(not(any(feature = "ssr", feature = "csr")))]
366mod feat_no_csr_ssr {
367    use super::*;
368
369    // Skeleton code to provide public methods when no renderer are enabled.
370    impl<COMP: BaseComponent> Scope<COMP> {
371        pub(super) fn arch_get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
372            Option::<&COMP>::None
373        }
374
375        pub(super) fn arch_send_message<T>(&self, _msg: T)
376        where
377            T: Into<COMP::Message>,
378        {
379        }
380
381        pub(super) fn arch_send_message_batch(&self, _messages: Vec<COMP::Message>) {}
382    }
383}
384
385#[cfg(any(feature = "ssr", feature = "csr"))]
386mod feat_csr_ssr {
387    use std::cell::{Ref, RefCell};
388    use std::sync::atomic::{AtomicUsize, Ordering};
389
390    use super::*;
391    use crate::html::component::lifecycle::UpdateRunner;
392    use crate::scheduler::{self, Shared};
393
394    #[derive(Debug)]
395    pub(crate) struct MsgQueue<Msg>(Shared<Vec<Msg>>);
396
397    impl<Msg> MsgQueue<Msg> {
398        pub fn new() -> Self {
399            MsgQueue(Rc::default())
400        }
401
402        pub fn push(&self, msg: Msg) -> usize {
403            let mut inner = self.0.borrow_mut();
404            inner.push(msg);
405
406            inner.len()
407        }
408
409        pub fn append(&self, other: &mut Vec<Msg>) -> usize {
410            let mut inner = self.0.borrow_mut();
411            inner.append(other);
412
413            inner.len()
414        }
415
416        pub fn drain(&self) -> Vec<Msg> {
417            let mut other_queue = Vec::new();
418            let mut inner = self.0.borrow_mut();
419
420            std::mem::swap(&mut *inner, &mut other_queue);
421
422            other_queue
423        }
424    }
425
426    impl<Msg> Clone for MsgQueue<Msg> {
427        fn clone(&self) -> Self {
428            MsgQueue(self.0.clone())
429        }
430    }
431
432    static COMP_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
433
434    impl<COMP: BaseComponent> Scope<COMP> {
435        /// Crate a scope with an optional parent scope
436        pub(crate) fn new(parent: Option<AnyScope>) -> Self {
437            let parent = parent.map(Rc::new);
438
439            let state = Rc::new(RefCell::new(None));
440
441            let pending_messages = MsgQueue::new();
442
443            Scope {
444                _marker: PhantomData,
445
446                pending_messages,
447
448                state,
449                parent,
450
451                id: COMP_ID_COUNTER.fetch_add(1, Ordering::SeqCst),
452            }
453        }
454
455        #[inline]
456        pub(super) fn arch_get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
457            self.state.try_borrow().ok().and_then(|state_ref| {
458                Ref::filter_map(state_ref, |state| {
459                    state.as_ref().and_then(|m| m.downcast_comp_ref::<COMP>())
460                })
461                .ok()
462            })
463        }
464
465        #[inline]
466        fn schedule_update(&self) {
467            scheduler::push_component_update(Box::new(UpdateRunner {
468                state: self.state.clone(),
469            }));
470            // Not guaranteed to already have the scheduler started
471            scheduler::start();
472        }
473
474        #[inline]
475        pub(super) fn arch_send_message<T>(&self, msg: T)
476        where
477            T: Into<COMP::Message>,
478        {
479            // We are the first message in queue, so we queue the update.
480            if self.pending_messages.push(msg.into()) == 1 {
481                self.schedule_update();
482            }
483        }
484
485        #[inline]
486        pub(super) fn arch_send_message_batch(&self, mut messages: Vec<COMP::Message>) {
487            let msg_len = messages.len();
488
489            // The queue was empty, so we queue the update
490            if self.pending_messages.append(&mut messages) == msg_len {
491                self.schedule_update();
492            }
493        }
494    }
495}
496
497#[cfg(any(feature = "ssr", feature = "csr"))]
498pub(crate) use feat_csr_ssr::*;
499
500#[cfg(feature = "csr")]
501mod feat_csr {
502    use std::cell::Ref;
503
504    use web_sys::Element;
505
506    use super::*;
507    use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
508    use crate::html::component::lifecycle::{
509        ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner,
510    };
511    use crate::scheduler;
512
513    impl AnyScope {
514        #[cfg(any(test, feature = "test"))]
515        pub(crate) fn test() -> Self {
516            Self {
517                type_id: TypeId::of::<()>(),
518                parent: None,
519                typed_scope: Rc::new(()),
520            }
521        }
522    }
523
524    fn schedule_props_update(
525        state: Shared<Option<ComponentState>>,
526        props: Rc<dyn Any>,
527        next_sibling_slot: DomSlot,
528    ) {
529        scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
530            state,
531            next_sibling_slot: Some(next_sibling_slot),
532            props: Some(props),
533        }));
534        // Not guaranteed to already have the scheduler started
535        scheduler::start();
536    }
537
538    impl<COMP> Scope<COMP>
539    where
540        COMP: BaseComponent,
541    {
542        /// Mounts a component with `props` to the specified `element` in the DOM.
543        pub(crate) fn mount_in_place(
544            &self,
545            root: BSubtree,
546            parent: Element,
547            slot: DomSlot,
548            props: Rc<COMP::Properties>,
549        ) -> DynamicDomSlot {
550            let bundle = Bundle::new();
551            let sibling_slot = DynamicDomSlot::new(slot);
552            let own_slot = DynamicDomSlot::new(sibling_slot.to_position());
553            let shared_slot = own_slot.clone();
554
555            let state = ComponentRenderState::Render {
556                bundle,
557                root,
558                own_slot,
559                parent,
560                sibling_slot,
561            };
562
563            scheduler::push_component_create(
564                self.id,
565                Box::new(CreateRunner {
566                    initial_render_state: state,
567                    props,
568                    scope: self.clone(),
569                    #[cfg(feature = "hydration")]
570                    prepared_state: None,
571                }),
572                Box::new(RenderRunner {
573                    state: self.state.clone(),
574                }),
575            );
576            // Not guaranteed to already have the scheduler started
577            scheduler::start();
578            shared_slot
579        }
580
581        pub(crate) fn reuse(&self, props: Rc<COMP::Properties>, slot: DomSlot) {
582            schedule_props_update(self.state.clone(), props, slot)
583        }
584    }
585
586    pub(crate) trait Scoped {
587        fn to_any(&self) -> AnyScope;
588        /// Get the render state if it hasn't already been destroyed
589        fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>>;
590        /// Shift the node associated with this scope to a new place
591        fn shift_node(&self, parent: Element, slot: DomSlot);
592        /// Process an event to destroy a component
593        fn destroy(self, parent_to_detach: bool);
594        fn destroy_boxed(self: Box<Self>, parent_to_detach: bool);
595    }
596
597    impl<COMP: BaseComponent> Scoped for Scope<COMP> {
598        fn to_any(&self) -> AnyScope {
599            self.clone().into()
600        }
601
602        fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>> {
603            let state_ref = self.state.borrow();
604
605            // check that component hasn't been destroyed
606            state_ref.as_ref()?;
607
608            Some(Ref::map(state_ref, |state_ref| {
609                &state_ref.as_ref().unwrap().render_state
610            }))
611        }
612
613        /// Process an event to destroy a component
614        fn destroy(self, parent_to_detach: bool) {
615            scheduler::push_component_destroy(Box::new(DestroyRunner {
616                state: self.state,
617                parent_to_detach,
618            }));
619            // Not guaranteed to already have the scheduler started
620            scheduler::start();
621        }
622
623        fn destroy_boxed(self: Box<Self>, parent_to_detach: bool) {
624            self.destroy(parent_to_detach)
625        }
626
627        fn shift_node(&self, parent: Element, slot: DomSlot) {
628            let mut state_ref = self.state.borrow_mut();
629            if let Some(render_state) = state_ref.as_mut() {
630                render_state.render_state.shift(parent, slot)
631            }
632        }
633    }
634}
635#[cfg(feature = "csr")]
636pub(crate) use feat_csr::*;
637
638#[cfg(feature = "hydration")]
639mod feat_hydration {
640    use wasm_bindgen::JsCast;
641    use web_sys::{Element, HtmlScriptElement};
642
643    use super::*;
644    use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot, Fragment};
645    use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner};
646    use crate::scheduler;
647    use crate::virtual_dom::Collectable;
648
649    impl<COMP> Scope<COMP>
650    where
651        COMP: BaseComponent,
652    {
653        /// Hydrates the component.
654        ///
655        /// Returns the position of the hydrated node in DOM.
656        ///
657        /// # Note
658        ///
659        /// This method is expected to collect all the elements belongs to the current component
660        /// immediately.
661        pub(crate) fn hydrate_in_place(
662            &self,
663            root: BSubtree,
664            parent: Element,
665            fragment: &mut Fragment,
666            props: Rc<COMP::Properties>,
667            prev_next_sibling: &mut Option<DynamicDomSlot>,
668        ) -> DynamicDomSlot {
669            // This is very helpful to see which component is failing during hydration
670            // which means this component may not having a stable layout / differs between
671            // client-side and server-side.
672            tracing::trace!(
673                component.id = self.id,
674                "hydration(type = {})",
675                std::any::type_name::<COMP>()
676            );
677
678            let collectable = Collectable::for_component::<COMP>();
679
680            let mut fragment = Fragment::collect_between(fragment, &collectable, &parent);
681
682            let prepared_state = match fragment
683                .back()
684                .cloned()
685                .and_then(|m| m.dyn_into::<HtmlScriptElement>().ok())
686            {
687                Some(m) if m.type_() == "application/x-yew-comp-state" => {
688                    fragment.pop_back();
689                    parent.remove_child(&m).unwrap();
690                    Some(m.text().unwrap())
691                }
692                _ => None,
693            };
694
695            let own_slot = match fragment.front().cloned() {
696                Some(first_node) => DynamicDomSlot::new(DomSlot::at(first_node)),
697                None => DynamicDomSlot::new(DomSlot::at_end()),
698            };
699            let shared_slot = own_slot.clone();
700            let sibling_slot = DynamicDomSlot::new_debug_trapped();
701            if let Some(prev_next_sibling) = prev_next_sibling {
702                prev_next_sibling.reassign(shared_slot.to_position());
703            }
704            *prev_next_sibling = Some(sibling_slot.clone());
705            let state = ComponentRenderState::Hydration {
706                parent,
707                root,
708                own_slot,
709                sibling_slot,
710                fragment,
711            };
712
713            scheduler::push_component_create(
714                self.id,
715                Box::new(CreateRunner {
716                    initial_render_state: state,
717                    props,
718                    scope: self.clone(),
719                    prepared_state,
720                }),
721                Box::new(RenderRunner {
722                    state: self.state.clone(),
723                }),
724            );
725
726            // Not guaranteed to already have the scheduler started
727            scheduler::start();
728            shared_slot
729        }
730    }
731}
732
733/// Defines a message type that can be sent to a component.
734/// Used for the return value of closure given to
735/// [Scope::batch_callback](struct.Scope.html#method.batch_callback).
736pub trait SendAsMessage<COMP: BaseComponent> {
737    /// Sends the message to the given component's scope.
738    /// See [Scope::batch_callback](struct.Scope.html#method.batch_callback).
739    fn send(self, scope: &Scope<COMP>);
740}
741
742impl<COMP> SendAsMessage<COMP> for Option<COMP::Message>
743where
744    COMP: BaseComponent,
745{
746    fn send(self, scope: &Scope<COMP>) {
747        if let Some(msg) = self {
748            scope.send_message(msg);
749        }
750    }
751}
752
753impl<COMP> SendAsMessage<COMP> for Vec<COMP::Message>
754where
755    COMP: BaseComponent,
756{
757    fn send(self, scope: &Scope<COMP>) {
758        scope.send_message_batch(self);
759    }
760}